1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.keyguard; 18 19 import android.app.ActivityManager; 20 import android.app.AlarmManager; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.BroadcastReceiver; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.graphics.drawable.Icon; 29 import android.icu.text.DateFormat; 30 import android.icu.text.DisplayContext; 31 import android.net.Uri; 32 import android.os.Handler; 33 import android.provider.Settings; 34 import android.service.notification.ZenModeConfig; 35 import android.text.TextUtils; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.systemui.R; 39 import com.android.systemui.statusbar.policy.NextAlarmController; 40 import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; 41 import com.android.systemui.statusbar.policy.ZenModeController; 42 import com.android.systemui.statusbar.policy.ZenModeControllerImpl; 43 44 import java.util.Date; 45 import java.util.Locale; 46 import java.util.concurrent.TimeUnit; 47 48 import androidx.slice.Slice; 49 import androidx.slice.SliceProvider; 50 import androidx.slice.builders.ListBuilder; 51 import androidx.slice.builders.ListBuilder.RowBuilder; 52 import androidx.slice.builders.SliceAction; 53 54 /** 55 * Simple Slice provider that shows the current date. 56 */ 57 public class KeyguardSliceProvider extends SliceProvider implements 58 NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback { 59 60 public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; 61 public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; 62 public static final String KEYGUARD_NEXT_ALARM_URI = 63 "content://com.android.systemui.keyguard/alarm"; 64 public static final String KEYGUARD_DND_URI = "content://com.android.systemui.keyguard/dnd"; 65 public static final String KEYGUARD_ACTION_URI = 66 "content://com.android.systemui.keyguard/action"; 67 68 /** 69 * Only show alarms that will ring within N hours. 70 */ 71 @VisibleForTesting 72 static final int ALARM_VISIBILITY_HOURS = 12; 73 74 protected final Uri mSliceUri; 75 protected final Uri mDateUri; 76 protected final Uri mAlarmUri; 77 protected final Uri mDndUri; 78 private final Date mCurrentTime = new Date(); 79 private final Handler mHandler; 80 private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm; 81 private ZenModeController mZenModeController; 82 private String mDatePattern; 83 private DateFormat mDateFormat; 84 private String mLastText; 85 private boolean mRegistered; 86 private String mNextAlarm; 87 private NextAlarmController mNextAlarmController; 88 protected AlarmManager mAlarmManager; 89 protected ContentResolver mContentResolver; 90 private AlarmManager.AlarmClockInfo mNextAlarmInfo; 91 92 /** 93 * Receiver responsible for time ticking and updating the date format. 94 */ 95 @VisibleForTesting 96 final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 97 @Override 98 public void onReceive(Context context, Intent intent) { 99 final String action = intent.getAction(); 100 if (Intent.ACTION_TIME_TICK.equals(action) 101 || Intent.ACTION_DATE_CHANGED.equals(action) 102 || Intent.ACTION_TIME_CHANGED.equals(action) 103 || Intent.ACTION_TIMEZONE_CHANGED.equals(action) 104 || Intent.ACTION_LOCALE_CHANGED.equals(action)) { 105 if (Intent.ACTION_LOCALE_CHANGED.equals(action) 106 || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { 107 // need to get a fresh date format 108 mHandler.post(KeyguardSliceProvider.this::cleanDateFormat); 109 } 110 mHandler.post(KeyguardSliceProvider.this::updateClock); 111 } 112 } 113 }; 114 KeyguardSliceProvider()115 public KeyguardSliceProvider() { 116 this(new Handler()); 117 } 118 119 @VisibleForTesting KeyguardSliceProvider(Handler handler)120 KeyguardSliceProvider(Handler handler) { 121 mHandler = handler; 122 mSliceUri = Uri.parse(KEYGUARD_SLICE_URI); 123 mDateUri = Uri.parse(KEYGUARD_DATE_URI); 124 mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); 125 mDndUri = Uri.parse(KEYGUARD_DND_URI); 126 } 127 128 @Override onBindSlice(Uri sliceUri)129 public Slice onBindSlice(Uri sliceUri) { 130 ListBuilder builder = new ListBuilder(getContext(), mSliceUri); 131 builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText)); 132 addNextAlarm(builder); 133 addZenMode(builder); 134 addPrimaryAction(builder); 135 return builder.build(); 136 } 137 addPrimaryAction(ListBuilder builder)138 protected void addPrimaryAction(ListBuilder builder) { 139 // Add simple action because API requires it; Keyguard handles presenting 140 // its own slices so this action + icon are actually never used. 141 PendingIntent pi = PendingIntent.getActivity(getContext(), 0, 142 new Intent(getContext(), KeyguardSliceProvider.class), 0); 143 Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); 144 SliceAction action = new SliceAction(pi, icon, mLastText); 145 146 RowBuilder primaryActionRow = new RowBuilder(builder, Uri.parse(KEYGUARD_ACTION_URI)) 147 .setPrimaryAction(action); 148 builder.addRow(primaryActionRow); 149 } 150 addNextAlarm(ListBuilder builder)151 protected void addNextAlarm(ListBuilder builder) { 152 if (TextUtils.isEmpty(mNextAlarm)) { 153 return; 154 } 155 156 Icon alarmIcon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); 157 RowBuilder alarmRowBuilder = new RowBuilder(builder, mAlarmUri) 158 .setTitle(mNextAlarm) 159 .addEndItem(alarmIcon); 160 builder.addRow(alarmRowBuilder); 161 } 162 163 /** 164 * Add zen mode (DND) icon to slice if it's enabled. 165 * @param builder The slice builder. 166 */ addZenMode(ListBuilder builder)167 protected void addZenMode(ListBuilder builder) { 168 if (!isDndSuppressingNotifications()) { 169 return; 170 } 171 RowBuilder dndBuilder = new RowBuilder(builder, mDndUri) 172 .setContentDescription(getContext().getResources() 173 .getString(R.string.accessibility_quick_settings_dnd)) 174 .addEndItem(Icon.createWithResource(getContext(), R.drawable.stat_sys_dnd)); 175 builder.addRow(dndBuilder); 176 } 177 178 /** 179 * Return true if DND is enabled suppressing notifications. 180 */ isDndSuppressingNotifications()181 protected boolean isDndSuppressingNotifications() { 182 boolean suppressingNotifications = (mZenModeController.getConfig().suppressedVisualEffects 183 & NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0; 184 return mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF 185 && suppressingNotifications; 186 } 187 188 @Override onCreateSliceProvider()189 public boolean onCreateSliceProvider() { 190 mAlarmManager = getContext().getSystemService(AlarmManager.class); 191 mContentResolver = getContext().getContentResolver(); 192 mNextAlarmController = new NextAlarmControllerImpl(getContext()); 193 mNextAlarmController.addCallback(this); 194 mZenModeController = new ZenModeControllerImpl(getContext(), mHandler); 195 mZenModeController.addCallback(this); 196 mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern); 197 registerClockUpdate(); 198 updateClock(); 199 return true; 200 } 201 202 @Override onZenChanged(int zen)203 public void onZenChanged(int zen) { 204 mContentResolver.notifyChange(mSliceUri, null /* observer */); 205 } 206 207 @Override onConfigChanged(ZenModeConfig config)208 public void onConfigChanged(ZenModeConfig config) { 209 mContentResolver.notifyChange(mSliceUri, null /* observer */); 210 } 211 updateNextAlarm()212 private void updateNextAlarm() { 213 if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) { 214 String pattern = android.text.format.DateFormat.is24HourFormat(getContext(), 215 ActivityManager.getCurrentUser()) ? "H:mm" : "h:mm"; 216 mNextAlarm = android.text.format.DateFormat.format(pattern, 217 mNextAlarmInfo.getTriggerTime()).toString(); 218 } else { 219 mNextAlarm = ""; 220 } 221 mContentResolver.notifyChange(mSliceUri, null /* observer */); 222 } 223 withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours)224 private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) { 225 if (alarmClockInfo == null) { 226 return false; 227 } 228 229 long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours); 230 return mNextAlarmInfo.getTriggerTime() <= limit; 231 } 232 233 /** 234 * Registers a broadcast receiver for clock updates, include date, time zone and manually 235 * changing the date/time via the settings app. 236 */ registerClockUpdate()237 private void registerClockUpdate() { 238 if (mRegistered) { 239 return; 240 } 241 242 IntentFilter filter = new IntentFilter(); 243 filter.addAction(Intent.ACTION_DATE_CHANGED); 244 filter.addAction(Intent.ACTION_TIME_CHANGED); 245 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 246 filter.addAction(Intent.ACTION_LOCALE_CHANGED); 247 getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/, 248 null /* scheduler */); 249 mRegistered = true; 250 } 251 252 @VisibleForTesting isRegistered()253 boolean isRegistered() { 254 return mRegistered; 255 } 256 updateClock()257 protected void updateClock() { 258 final String text = getFormattedDate(); 259 if (!text.equals(mLastText)) { 260 mLastText = text; 261 mContentResolver.notifyChange(mSliceUri, null /* observer */); 262 } 263 } 264 getFormattedDate()265 protected String getFormattedDate() { 266 if (mDateFormat == null) { 267 final Locale l = Locale.getDefault(); 268 DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l); 269 format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE); 270 mDateFormat = format; 271 } 272 mCurrentTime.setTime(System.currentTimeMillis()); 273 return mDateFormat.format(mCurrentTime); 274 } 275 276 @VisibleForTesting cleanDateFormat()277 void cleanDateFormat() { 278 mDateFormat = null; 279 } 280 281 @Override onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm)282 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { 283 mNextAlarmInfo = nextAlarm; 284 mAlarmManager.cancel(mUpdateNextAlarm); 285 286 long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime() 287 - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS); 288 if (triggerAt > 0) { 289 mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm", 290 mUpdateNextAlarm, mHandler); 291 } 292 updateNextAlarm(); 293 } 294 } 295