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, new Intent(), 0); 142 Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); 143 SliceAction action = new SliceAction(pi, icon, mLastText); 144 145 RowBuilder primaryActionRow = new RowBuilder(builder, Uri.parse(KEYGUARD_ACTION_URI)) 146 .setPrimaryAction(action); 147 builder.addRow(primaryActionRow); 148 } 149 addNextAlarm(ListBuilder builder)150 protected void addNextAlarm(ListBuilder builder) { 151 if (TextUtils.isEmpty(mNextAlarm)) { 152 return; 153 } 154 155 Icon alarmIcon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); 156 RowBuilder alarmRowBuilder = new RowBuilder(builder, mAlarmUri) 157 .setTitle(mNextAlarm) 158 .addEndItem(alarmIcon); 159 builder.addRow(alarmRowBuilder); 160 } 161 162 /** 163 * Add zen mode (DND) icon to slice if it's enabled. 164 * @param builder The slice builder. 165 */ addZenMode(ListBuilder builder)166 protected void addZenMode(ListBuilder builder) { 167 if (!isDndSuppressingNotifications()) { 168 return; 169 } 170 RowBuilder dndBuilder = new RowBuilder(builder, mDndUri) 171 .setContentDescription(getContext().getResources() 172 .getString(R.string.accessibility_quick_settings_dnd)) 173 .addEndItem(Icon.createWithResource(getContext(), R.drawable.stat_sys_dnd)); 174 builder.addRow(dndBuilder); 175 } 176 177 /** 178 * Return true if DND is enabled suppressing notifications. 179 */ isDndSuppressingNotifications()180 protected boolean isDndSuppressingNotifications() { 181 boolean suppressingNotifications = (mZenModeController.getConfig().suppressedVisualEffects 182 & NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0; 183 return mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF 184 && suppressingNotifications; 185 } 186 187 @Override onCreateSliceProvider()188 public boolean onCreateSliceProvider() { 189 mAlarmManager = getContext().getSystemService(AlarmManager.class); 190 mContentResolver = getContext().getContentResolver(); 191 mNextAlarmController = new NextAlarmControllerImpl(getContext()); 192 mNextAlarmController.addCallback(this); 193 mZenModeController = new ZenModeControllerImpl(getContext(), mHandler); 194 mZenModeController.addCallback(this); 195 mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern); 196 registerClockUpdate(); 197 updateClock(); 198 return true; 199 } 200 201 @Override onZenChanged(int zen)202 public void onZenChanged(int zen) { 203 mContentResolver.notifyChange(mSliceUri, null /* observer */); 204 } 205 206 @Override onConfigChanged(ZenModeConfig config)207 public void onConfigChanged(ZenModeConfig config) { 208 mContentResolver.notifyChange(mSliceUri, null /* observer */); 209 } 210 updateNextAlarm()211 private void updateNextAlarm() { 212 if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) { 213 String pattern = android.text.format.DateFormat.is24HourFormat(getContext(), 214 ActivityManager.getCurrentUser()) ? "H:mm" : "h:mm"; 215 mNextAlarm = android.text.format.DateFormat.format(pattern, 216 mNextAlarmInfo.getTriggerTime()).toString(); 217 } else { 218 mNextAlarm = ""; 219 } 220 mContentResolver.notifyChange(mSliceUri, null /* observer */); 221 } 222 withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours)223 private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) { 224 if (alarmClockInfo == null) { 225 return false; 226 } 227 228 long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours); 229 return mNextAlarmInfo.getTriggerTime() <= limit; 230 } 231 232 /** 233 * Registers a broadcast receiver for clock updates, include date, time zone and manually 234 * changing the date/time via the settings app. 235 */ registerClockUpdate()236 private void registerClockUpdate() { 237 if (mRegistered) { 238 return; 239 } 240 241 IntentFilter filter = new IntentFilter(); 242 filter.addAction(Intent.ACTION_DATE_CHANGED); 243 filter.addAction(Intent.ACTION_TIME_CHANGED); 244 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 245 filter.addAction(Intent.ACTION_LOCALE_CHANGED); 246 getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/, 247 null /* scheduler */); 248 mRegistered = true; 249 } 250 251 @VisibleForTesting isRegistered()252 boolean isRegistered() { 253 return mRegistered; 254 } 255 updateClock()256 protected void updateClock() { 257 final String text = getFormattedDate(); 258 if (!text.equals(mLastText)) { 259 mLastText = text; 260 mContentResolver.notifyChange(mSliceUri, null /* observer */); 261 } 262 } 263 getFormattedDate()264 protected String getFormattedDate() { 265 if (mDateFormat == null) { 266 final Locale l = Locale.getDefault(); 267 DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l); 268 format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE); 269 mDateFormat = format; 270 } 271 mCurrentTime.setTime(System.currentTimeMillis()); 272 return mDateFormat.format(mCurrentTime); 273 } 274 275 @VisibleForTesting cleanDateFormat()276 void cleanDateFormat() { 277 mDateFormat = null; 278 } 279 280 @Override onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm)281 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { 282 mNextAlarmInfo = nextAlarm; 283 mAlarmManager.cancel(mUpdateNextAlarm); 284 285 long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime() 286 - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS); 287 if (triggerAt > 0) { 288 mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm", 289 mUpdateNextAlarm, mHandler); 290 } 291 updateNextAlarm(); 292 } 293 } 294