blob: 5795dcce861c007785dad338544e35c0f36c946f [file] [log] [blame]
Lucas Dupin957e50c2017-10-10 11:23:27 -07001/*
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
17package com.android.systemui.keyguard;
18
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080019import android.annotation.AnyThread;
Lucas Dupin9cc40de2017-12-19 16:09:32 -080020import android.app.ActivityManager;
21import android.app.AlarmManager;
Mady Mellor78f04ef2018-05-23 15:04:47 -070022import android.app.PendingIntent;
Lucas Dupin957e50c2017-10-10 11:23:27 -070023import android.content.BroadcastReceiver;
Lucas Dupin1f7374a2018-02-26 18:08:33 -080024import android.content.ContentResolver;
Lucas Dupin957e50c2017-10-10 11:23:27 -070025import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080028import android.graphics.Typeface;
29import android.graphics.drawable.Icon;
Lucas Dupin957e50c2017-10-10 11:23:27 -070030import android.icu.text.DateFormat;
31import android.icu.text.DisplayContext;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080032import android.media.MediaMetadata;
Lucas Dupin6b40d5e2019-05-15 19:47:11 -070033import android.media.session.PlaybackState;
Lucas Dupin957e50c2017-10-10 11:23:27 -070034import android.net.Uri;
35import android.os.Handler;
Lucas Dupina0d51192018-08-15 11:29:32 -070036import android.os.Trace;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -070037import android.provider.Settings;
38import android.service.notification.ZenModeConfig;
Lucas Dupin9cc40de2017-12-19 16:09:32 -080039import android.text.TextUtils;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080040import android.text.style.StyleSpan;
Lucas Dupin957e50c2017-10-10 11:23:27 -070041
Lucas Dupin64d1e122018-11-12 14:30:36 -080042import androidx.core.graphics.drawable.IconCompat;
43import androidx.slice.Slice;
44import androidx.slice.SliceProvider;
45import androidx.slice.builders.ListBuilder;
46import androidx.slice.builders.ListBuilder.RowBuilder;
47import androidx.slice.builders.SliceAction;
48
Lucas Dupin957e50c2017-10-10 11:23:27 -070049import com.android.internal.annotations.VisibleForTesting;
Lucas Dupin7e171e22018-12-20 11:29:35 -080050import com.android.keyguard.KeyguardUpdateMonitor;
51import com.android.keyguard.KeyguardUpdateMonitorCallback;
Lucas Dupin52ee5a52019-10-16 16:39:00 -070052import com.android.systemui.Dependency;
Lucas Dupin957e50c2017-10-10 11:23:27 -070053import com.android.systemui.R;
Beverly8fdb5332019-02-04 14:29:49 -050054import com.android.systemui.plugins.statusbar.StatusBarStateController;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080055import com.android.systemui.statusbar.NotificationMediaManager;
Robert Snoebergerdc8fee72019-07-19 16:35:35 -040056import com.android.systemui.statusbar.StatusBarState;
Selim Cineke3c6e462019-06-24 19:37:06 -070057import com.android.systemui.statusbar.phone.DozeParameters;
Lucas Dupin940e4902019-06-03 15:43:20 -070058import com.android.systemui.statusbar.phone.KeyguardBypassController;
Lucas Dupin9cc40de2017-12-19 16:09:32 -080059import com.android.systemui.statusbar.policy.NextAlarmController;
60import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -070061import com.android.systemui.statusbar.policy.ZenModeController;
Lucas Dupin9027bda2019-05-28 14:34:47 -070062import com.android.systemui.util.wakelock.SettableWakeLock;
63import com.android.systemui.util.wakelock.WakeLock;
Lucas Dupin957e50c2017-10-10 11:23:27 -070064
65import java.util.Date;
66import java.util.Locale;
Lucas Dupin7e171e22018-12-20 11:29:35 -080067import java.util.TimeZone;
Lucas Dupin1f7374a2018-02-26 18:08:33 -080068import java.util.concurrent.TimeUnit;
Lucas Dupin957e50c2017-10-10 11:23:27 -070069
70/**
71 * Simple Slice provider that shows the current date.
72 */
Lucas Dupin9cc40de2017-12-19 16:09:32 -080073public class KeyguardSliceProvider extends SliceProvider implements
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080074 NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
Lucas Dupin3d560e42019-01-07 13:52:44 -080075 NotificationMediaManager.MediaListener, StatusBarStateController.StateListener {
Lucas Dupin957e50c2017-10-10 11:23:27 -070076
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080077 private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
Lucas Dupin957e50c2017-10-10 11:23:27 -070078 public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
Lucas Dupine570af62019-02-10 14:52:30 -080079 private static final String KEYGUARD_HEADER_URI =
80 "content://com.android.systemui.keyguard/header";
Lucas Dupin6bd86012017-12-05 17:58:57 -080081 public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
Lucas Dupin9cc40de2017-12-19 16:09:32 -080082 public static final String KEYGUARD_NEXT_ALARM_URI =
83 "content://com.android.systemui.keyguard/alarm";
Lucas Dupin8b77a3b2018-05-01 13:23:03 -070084 public static final String KEYGUARD_DND_URI = "content://com.android.systemui.keyguard/dnd";
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080085 public static final String KEYGUARD_MEDIA_URI =
86 "content://com.android.systemui.keyguard/media";
Mady Mellor78f04ef2018-05-23 15:04:47 -070087 public static final String KEYGUARD_ACTION_URI =
88 "content://com.android.systemui.keyguard/action";
Lucas Dupin957e50c2017-10-10 11:23:27 -070089
Lucas Dupin1f7374a2018-02-26 18:08:33 -080090 /**
91 * Only show alarms that will ring within N hours.
92 */
93 @VisibleForTesting
94 static final int ALARM_VISIBILITY_HOURS = 12;
95
Lucas Dupina0d51192018-08-15 11:29:32 -070096 private static KeyguardSliceProvider sInstance;
97
Lucas Dupin957e50c2017-10-10 11:23:27 -070098 protected final Uri mSliceUri;
Lucas Dupine570af62019-02-10 14:52:30 -080099 protected final Uri mHeaderUri;
Lucas Dupin6bd86012017-12-05 17:58:57 -0800100 protected final Uri mDateUri;
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800101 protected final Uri mAlarmUri;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700102 protected final Uri mDndUri;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800103 protected final Uri mMediaUri;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700104 private final Date mCurrentTime = new Date();
Lucas Dupin957e50c2017-10-10 11:23:27 -0700105 private final Handler mHandler;
Lucas Dupin52ee5a52019-10-16 16:39:00 -0700106 private final Handler mMediaHandler;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700107 private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm;
Selim Cineke3c6e462019-06-24 19:37:06 -0700108 private DozeParameters mDozeParameters;
Lucas Dupin2d345232019-06-20 14:07:57 -0700109 @VisibleForTesting
110 protected SettableWakeLock mMediaWakeLock;
111 @VisibleForTesting
112 protected ZenModeController mZenModeController;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700113 private String mDatePattern;
114 private DateFormat mDateFormat;
115 private String mLastText;
116 private boolean mRegistered;
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800117 private String mNextAlarm;
118 private NextAlarmController mNextAlarmController;
Lucas Dupin2d345232019-06-20 14:07:57 -0700119 @VisibleForTesting
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800120 protected AlarmManager mAlarmManager;
Lucas Dupin2d345232019-06-20 14:07:57 -0700121 @VisibleForTesting
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800122 protected ContentResolver mContentResolver;
123 private AlarmManager.AlarmClockInfo mNextAlarmInfo;
Lucas Dupina0d51192018-08-15 11:29:32 -0700124 private PendingIntent mPendingIntent;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800125 protected NotificationMediaManager mMediaManager;
Lucas Dupin3d560e42019-01-07 13:52:44 -0800126 private StatusBarStateController mStatusBarStateController;
Lucas Dupin940e4902019-06-03 15:43:20 -0700127 private KeyguardBypassController mKeyguardBypassController;
Lucas Dupin9027bda2019-05-28 14:34:47 -0700128 private CharSequence mMediaTitle;
129 private CharSequence mMediaArtist;
Lucas Dupin3d560e42019-01-07 13:52:44 -0800130 protected boolean mDozing;
Robert Snoebergerdc8fee72019-07-19 16:35:35 -0400131 private int mStatusBarState;
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700132 private boolean mMediaIsVisible;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700133
134 /**
135 * Receiver responsible for time ticking and updating the date format.
136 */
137 @VisibleForTesting
138 final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
139 @Override
140 public void onReceive(Context context, Intent intent) {
141 final String action = intent.getAction();
Lucas Dupin7e171e22018-12-20 11:29:35 -0800142 if (Intent.ACTION_DATE_CHANGED.equals(action)) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800143 synchronized (this) {
144 updateClockLocked();
145 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800146 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800147 synchronized (this) {
148 cleanDateFormatLocked();
149 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700150 }
151 }
152 };
153
Lucas Dupin7e171e22018-12-20 11:29:35 -0800154 @VisibleForTesting
155 final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
156 new KeyguardUpdateMonitorCallback() {
157 @Override
158 public void onTimeChanged() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800159 synchronized (this) {
160 updateClockLocked();
161 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800162 }
163
164 @Override
165 public void onTimeZoneChanged(TimeZone timeZone) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800166 synchronized (this) {
167 cleanDateFormatLocked();
168 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800169 }
170 };
171
Lucas Dupina0d51192018-08-15 11:29:32 -0700172 public static KeyguardSliceProvider getAttachedInstance() {
173 return KeyguardSliceProvider.sInstance;
174 }
175
Lucas Dupin52ee5a52019-10-16 16:39:00 -0700176 public KeyguardSliceProvider() {
177 mHandler = new Handler();
178 mMediaHandler = new Handler();
Lucas Dupin957e50c2017-10-10 11:23:27 -0700179 mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
Lucas Dupine570af62019-02-10 14:52:30 -0800180 mHeaderUri = Uri.parse(KEYGUARD_HEADER_URI);
Lucas Dupin6bd86012017-12-05 17:58:57 -0800181 mDateUri = Uri.parse(KEYGUARD_DATE_URI);
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800182 mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI);
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700183 mDndUri = Uri.parse(KEYGUARD_DND_URI);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800184 mMediaUri = Uri.parse(KEYGUARD_MEDIA_URI);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700185 }
186
Lucas Dupin3d560e42019-01-07 13:52:44 -0800187 /**
188 * Initialize dependencies that don't exist during {@link android.content.ContentProvider}
189 * instantiation.
190 *
191 * @param mediaManager {@link NotificationMediaManager} singleton.
192 * @param statusBarStateController {@link StatusBarStateController} singleton.
193 */
194 public void initDependencies(
195 NotificationMediaManager mediaManager,
Lucas Dupin940e4902019-06-03 15:43:20 -0700196 StatusBarStateController statusBarStateController,
Selim Cineke3c6e462019-06-24 19:37:06 -0700197 KeyguardBypassController keyguardBypassController,
198 DozeParameters dozeParameters) {
Lucas Dupin3d560e42019-01-07 13:52:44 -0800199 mMediaManager = mediaManager;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800200 mMediaManager.addCallback(this);
Lucas Dupin3d560e42019-01-07 13:52:44 -0800201 mStatusBarStateController = statusBarStateController;
202 mStatusBarStateController.addCallback(this);
Lucas Dupin940e4902019-06-03 15:43:20 -0700203 mKeyguardBypassController = keyguardBypassController;
Selim Cineke3c6e462019-06-24 19:37:06 -0700204 mDozeParameters = dozeParameters;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800205 }
206
207 @AnyThread
Lucas Dupin957e50c2017-10-10 11:23:27 -0700208 @Override
209 public Slice onBindSlice(Uri sliceUri) {
Lucas Dupina0d51192018-08-15 11:29:32 -0700210 Trace.beginSection("KeyguardSliceProvider#onBindSlice");
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800211 Slice slice;
212 synchronized (this) {
213 ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
Lucas Dupin3d560e42019-01-07 13:52:44 -0800214 if (needsMediaLocked()) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800215 addMediaLocked(builder);
216 } else {
217 builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800218 }
Lucas Dupinc9548242019-04-11 10:59:13 -0700219 addNextAlarmLocked(builder);
220 addZenModeLocked(builder);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800221 addPrimaryActionLocked(builder);
222 slice = builder.build();
223 }
Lucas Dupina0d51192018-08-15 11:29:32 -0700224 Trace.endSection();
225 return slice;
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800226 }
227
Lucas Dupin3d560e42019-01-07 13:52:44 -0800228 protected boolean needsMediaLocked() {
Selim Cineke3c6e462019-06-24 19:37:06 -0700229 boolean keepWhenAwake = mKeyguardBypassController != null
230 && mKeyguardBypassController.getBypassEnabled() && mDozeParameters.getAlwaysOn();
Robert Snoebergerdc8fee72019-07-19 16:35:35 -0400231 // Show header if music is playing and the status bar is in the shade state. This way, an
232 // animation isn't necessary when pressing power and transitioning to AOD.
233 boolean keepWhenShade = mStatusBarState == StatusBarState.SHADE && mMediaIsVisible;
234 return !TextUtils.isEmpty(mMediaTitle) && mMediaIsVisible && (mDozing || keepWhenAwake
235 || keepWhenShade);
Lucas Dupin3d560e42019-01-07 13:52:44 -0800236 }
237
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800238 protected void addMediaLocked(ListBuilder listBuilder) {
Lucas Dupin9027bda2019-05-28 14:34:47 -0700239 if (TextUtils.isEmpty(mMediaTitle)) {
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700240 return;
241 }
Lucas Dupin9027bda2019-05-28 14:34:47 -0700242 listBuilder.setHeader(new ListBuilder.HeaderBuilder(mHeaderUri).setTitle(mMediaTitle));
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700243
Lucas Dupin9027bda2019-05-28 14:34:47 -0700244 if (!TextUtils.isEmpty(mMediaArtist)) {
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700245 RowBuilder albumBuilder = new RowBuilder(mMediaUri);
Lucas Dupin9027bda2019-05-28 14:34:47 -0700246 albumBuilder.setTitle(mMediaArtist);
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700247
248 Icon mediaIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon();
249 IconCompat mediaIconCompat = mediaIcon == null ? null
250 : IconCompat.createFromIcon(getContext(), mediaIcon);
251 if (mediaIconCompat != null) {
252 albumBuilder.addEndItem(mediaIconCompat, ListBuilder.ICON_IMAGE);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800253 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800254
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700255 listBuilder.addRow(albumBuilder);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800256 }
257 }
258
259 protected void addPrimaryActionLocked(ListBuilder builder) {
Mady Mellor78f04ef2018-05-23 15:04:47 -0700260 // Add simple action because API requires it; Keyguard handles presenting
261 // its own slices so this action + icon are actually never used.
Jason Monk1045e0b2018-08-06 09:42:10 -0400262 IconCompat icon = IconCompat.createWithResource(getContext(),
263 R.drawable.ic_access_alarms_big);
Lucas Dupina0d51192018-08-15 11:29:32 -0700264 SliceAction action = SliceAction.createDeeplink(mPendingIntent, icon,
Jason Monk1045e0b2018-08-06 09:42:10 -0400265 ListBuilder.ICON_IMAGE, mLastText);
Jason Monk1045e0b2018-08-06 09:42:10 -0400266 RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI))
267 .setPrimaryAction(action);
Mady Mellor78f04ef2018-05-23 15:04:47 -0700268 builder.addRow(primaryActionRow);
269 }
270
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800271 protected void addNextAlarmLocked(ListBuilder builder) {
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800272 if (TextUtils.isEmpty(mNextAlarm)) {
273 return;
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800274 }
Jason Monk1045e0b2018-08-06 09:42:10 -0400275 IconCompat alarmIcon = IconCompat.createWithResource(getContext(),
276 R.drawable.ic_access_alarms_big);
277 RowBuilder alarmRowBuilder = new RowBuilder(mAlarmUri)
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800278 .setTitle(mNextAlarm)
Jason Monk1045e0b2018-08-06 09:42:10 -0400279 .addEndItem(alarmIcon, ListBuilder.ICON_IMAGE);
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800280 builder.addRow(alarmRowBuilder);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700281 }
282
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700283 /**
284 * Add zen mode (DND) icon to slice if it's enabled.
285 * @param builder The slice builder.
286 */
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800287 protected void addZenModeLocked(ListBuilder builder) {
Lucas Dupin627ad372018-11-27 10:28:25 +0100288 if (!isDndOn()) {
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700289 return;
290 }
Jason Monk1045e0b2018-08-06 09:42:10 -0400291 RowBuilder dndBuilder = new RowBuilder(mDndUri)
Lucas Dupin1d5e9102018-05-21 17:58:05 -0700292 .setContentDescription(getContext().getResources()
293 .getString(R.string.accessibility_quick_settings_dnd))
Amin Shaikhad29e932019-03-08 11:26:12 -0500294 .addEndItem(
295 IconCompat.createWithResource(getContext(), R.drawable.stat_sys_dnd),
296 ListBuilder.ICON_IMAGE);
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700297 builder.addRow(dndBuilder);
298 }
299
300 /**
Lucas Dupin627ad372018-11-27 10:28:25 +0100301 * Return true if DND is enabled.
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700302 */
Lucas Dupin627ad372018-11-27 10:28:25 +0100303 protected boolean isDndOn() {
304 return mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700305 }
306
Lucas Dupin957e50c2017-10-10 11:23:27 -0700307 @Override
Lucas Dupin6bd86012017-12-05 17:58:57 -0800308 public boolean onCreateSliceProvider() {
Lucas Dupin2d345232019-06-20 14:07:57 -0700309 synchronized (this) {
310 KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance;
311 if (oldInstance != null) {
312 oldInstance.onDestroy();
313 }
314
315 mAlarmManager = getContext().getSystemService(AlarmManager.class);
316 mContentResolver = getContext().getContentResolver();
317 mNextAlarmController = new NextAlarmControllerImpl(getContext());
318 mNextAlarmController.addCallback(this);
Lucas Dupin52ee5a52019-10-16 16:39:00 -0700319 mZenModeController = Dependency.get(ZenModeController.class);
Lucas Dupin2d345232019-06-20 14:07:57 -0700320 mZenModeController.addCallback(this);
321 mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
322 mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
323 mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
324 "media");
325 KeyguardSliceProvider.sInstance = this;
326 registerClockUpdate();
327 updateClockLocked();
328 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700329 return true;
330 }
331
Lucas Dupin2d345232019-06-20 14:07:57 -0700332 @VisibleForTesting
333 protected void onDestroy() {
334 synchronized (this) {
335 mNextAlarmController.removeCallback(this);
336 mZenModeController.removeCallback(this);
337 mMediaWakeLock.setAcquired(false);
338 mAlarmManager.cancel(mUpdateNextAlarm);
339 if (mRegistered) {
340 mRegistered = false;
341 getKeyguardUpdateMonitor().removeCallback(mKeyguardUpdateMonitorCallback);
342 getContext().unregisterReceiver(mIntentReceiver);
343 }
344 }
345 }
346
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700347 @Override
348 public void onZenChanged(int zen) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800349 notifyChange();
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700350 }
351
352 @Override
353 public void onConfigChanged(ZenModeConfig config) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800354 notifyChange();
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700355 }
356
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800357 private void updateNextAlarm() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800358 synchronized (this) {
359 if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
360 String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
361 ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm";
362 mNextAlarm = android.text.format.DateFormat.format(pattern,
363 mNextAlarmInfo.getTriggerTime()).toString();
364 } else {
365 mNextAlarm = "";
366 }
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800367 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800368 notifyChange();
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800369 }
370
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800371 private boolean withinNHoursLocked(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) {
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800372 if (alarmClockInfo == null) {
373 return false;
374 }
375
376 long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours);
377 return mNextAlarmInfo.getTriggerTime() <= limit;
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800378 }
379
Lucas Dupin6bd86012017-12-05 17:58:57 -0800380 /**
381 * Registers a broadcast receiver for clock updates, include date, time zone and manually
382 * changing the date/time via the settings app.
Lucas Dupin6bd86012017-12-05 17:58:57 -0800383 */
Lucas Dupin2d345232019-06-20 14:07:57 -0700384 @VisibleForTesting
385 protected void registerClockUpdate() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800386 synchronized (this) {
387 if (mRegistered) {
388 return;
389 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700390
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800391 IntentFilter filter = new IntentFilter();
392 filter.addAction(Intent.ACTION_DATE_CHANGED);
393 filter.addAction(Intent.ACTION_LOCALE_CHANGED);
394 getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
395 null /* scheduler */);
396 getKeyguardUpdateMonitor().registerCallback(mKeyguardUpdateMonitorCallback);
397 mRegistered = true;
398 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700399 }
400
401 @VisibleForTesting
402 boolean isRegistered() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800403 synchronized (this) {
404 return mRegistered;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700405 }
406 }
407
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800408 protected void updateClockLocked() {
409 final String text = getFormattedDateLocked();
410 if (!text.equals(mLastText)) {
411 mLastText = text;
412 notifyChange();
413 }
414 }
415
416 protected String getFormattedDateLocked() {
Lucas Dupin957e50c2017-10-10 11:23:27 -0700417 if (mDateFormat == null) {
418 final Locale l = Locale.getDefault();
419 DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
420 format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
421 mDateFormat = format;
422 }
423 mCurrentTime.setTime(System.currentTimeMillis());
424 return mDateFormat.format(mCurrentTime);
425 }
426
427 @VisibleForTesting
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800428 void cleanDateFormatLocked() {
Lucas Dupin957e50c2017-10-10 11:23:27 -0700429 mDateFormat = null;
430 }
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800431
432 @Override
433 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800434 synchronized (this) {
435 mNextAlarmInfo = nextAlarm;
436 mAlarmManager.cancel(mUpdateNextAlarm);
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800437
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800438 long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime()
439 - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS);
440 if (triggerAt > 0) {
441 mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm",
442 mUpdateNextAlarm, mHandler);
443 }
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800444 }
445 updateNextAlarm();
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800446 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800447
448 @VisibleForTesting
449 protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
450 return KeyguardUpdateMonitor.getInstance(getContext());
451 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800452
Lucas Dupin76527ee2019-02-20 10:46:23 -0800453 /**
454 * Called whenever new media metadata is available.
455 * @param metadata New metadata.
456 */
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800457 @Override
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700458 public void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800459 synchronized (this) {
Selim Cineke3c6e462019-06-24 19:37:06 -0700460 boolean nextVisible = NotificationMediaManager.isPlayingState(state);
Lucas Dupin52ee5a52019-10-16 16:39:00 -0700461 mMediaHandler.removeCallbacksAndMessages(null);
Robert Snoebergerdc8fee72019-07-19 16:35:35 -0400462 if (mMediaIsVisible && !nextVisible && mStatusBarState != StatusBarState.SHADE) {
Lucas Dupin9027bda2019-05-28 14:34:47 -0700463 // We need to delay this event for a few millis when stopping to avoid jank in the
464 // animation. The media app might not send its update when buffering, and the slice
465 // would end up without a header for 0.5 second.
466 mMediaWakeLock.setAcquired(true);
Lucas Dupin52ee5a52019-10-16 16:39:00 -0700467 mMediaHandler.postDelayed(() -> {
468 synchronized (this) {
469 updateMediaStateLocked(metadata, state);
470 mMediaWakeLock.setAcquired(false);
471 }
472 }, 2000);
Lucas Dupin9027bda2019-05-28 14:34:47 -0700473 } else {
474 mMediaWakeLock.setAcquired(false);
475 updateMediaStateLocked(metadata, state);
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700476 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800477 }
Lucas Dupin9027bda2019-05-28 14:34:47 -0700478 }
479
480 private void updateMediaStateLocked(MediaMetadata metadata, @PlaybackState.State int state) {
Selim Cineke3c6e462019-06-24 19:37:06 -0700481 boolean nextVisible = NotificationMediaManager.isPlayingState(state);
Lucas Dupin9027bda2019-05-28 14:34:47 -0700482 CharSequence title = null;
483 if (metadata != null) {
484 title = metadata.getText(MediaMetadata.METADATA_KEY_TITLE);
485 if (TextUtils.isEmpty(title)) {
486 title = getContext().getResources().getString(R.string.music_controls_no_title);
487 }
488 }
489 CharSequence artist = metadata == null ? null : metadata.getText(
490 MediaMetadata.METADATA_KEY_ARTIST);
491
492 if (nextVisible == mMediaIsVisible && TextUtils.equals(title, mMediaTitle)
493 && TextUtils.equals(artist, mMediaArtist)) {
494 return;
495 }
496 mMediaTitle = title;
497 mMediaArtist = artist;
498 mMediaIsVisible = nextVisible;
Lucas Dupin76527ee2019-02-20 10:46:23 -0800499 notifyChange();
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800500 }
501
502 protected void notifyChange() {
503 mContentResolver.notifyChange(mSliceUri, null /* observer */);
504 }
Lucas Dupin3d560e42019-01-07 13:52:44 -0800505
506 @Override
507 public void onDozingChanged(boolean isDozing) {
508 final boolean notify;
509 synchronized (this) {
510 boolean neededMedia = needsMediaLocked();
511 mDozing = isDozing;
512 notify = neededMedia != needsMediaLocked();
513 }
514 if (notify) {
515 notifyChange();
516 }
517 }
518
519 @Override
520 public void onStateChanged(int newState) {
Robert Snoebergerdc8fee72019-07-19 16:35:35 -0400521 final boolean notify;
522 synchronized (this) {
523 boolean needsMedia = needsMediaLocked();
524 mStatusBarState = newState;
525 notify = needsMedia != needsMediaLocked();
526 }
527 if (notify) {
528 notifyChange();
529 }
Lucas Dupin3d560e42019-01-07 13:52:44 -0800530 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700531}