blob: 20477975a6dd29f1bc1954bff2dfd88e54506837 [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 Dupin957e50c2017-10-10 11:23:27 -070052import com.android.systemui.R;
Beverly8fdb5332019-02-04 14:29:49 -050053import com.android.systemui.plugins.statusbar.StatusBarStateController;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080054import com.android.systemui.statusbar.NotificationMediaManager;
Lucas Dupin9cc40de2017-12-19 16:09:32 -080055import com.android.systemui.statusbar.policy.NextAlarmController;
56import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -070057import com.android.systemui.statusbar.policy.ZenModeController;
58import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
Lucas Dupin9027bda2019-05-28 14:34:47 -070059import com.android.systemui.util.wakelock.SettableWakeLock;
60import com.android.systemui.util.wakelock.WakeLock;
Lucas Dupin957e50c2017-10-10 11:23:27 -070061
62import java.util.Date;
Lucas Dupin6b40d5e2019-05-15 19:47:11 -070063import java.util.HashSet;
Lucas Dupin957e50c2017-10-10 11:23:27 -070064import java.util.Locale;
Lucas Dupin7e171e22018-12-20 11:29:35 -080065import java.util.TimeZone;
Lucas Dupin1f7374a2018-02-26 18:08:33 -080066import java.util.concurrent.TimeUnit;
Lucas Dupin957e50c2017-10-10 11:23:27 -070067
68/**
69 * Simple Slice provider that shows the current date.
70 */
Lucas Dupin9cc40de2017-12-19 16:09:32 -080071public class KeyguardSliceProvider extends SliceProvider implements
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080072 NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
Lucas Dupin3d560e42019-01-07 13:52:44 -080073 NotificationMediaManager.MediaListener, StatusBarStateController.StateListener {
Lucas Dupin957e50c2017-10-10 11:23:27 -070074
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080075 private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
Lucas Dupin957e50c2017-10-10 11:23:27 -070076 public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
Lucas Dupine570af62019-02-10 14:52:30 -080077 private static final String KEYGUARD_HEADER_URI =
78 "content://com.android.systemui.keyguard/header";
Lucas Dupin6bd86012017-12-05 17:58:57 -080079 public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
Lucas Dupin9cc40de2017-12-19 16:09:32 -080080 public static final String KEYGUARD_NEXT_ALARM_URI =
81 "content://com.android.systemui.keyguard/alarm";
Lucas Dupin8b77a3b2018-05-01 13:23:03 -070082 public static final String KEYGUARD_DND_URI = "content://com.android.systemui.keyguard/dnd";
Lucas Dupin6a03a9f2018-12-20 17:13:52 -080083 public static final String KEYGUARD_MEDIA_URI =
84 "content://com.android.systemui.keyguard/media";
Mady Mellor78f04ef2018-05-23 15:04:47 -070085 public static final String KEYGUARD_ACTION_URI =
86 "content://com.android.systemui.keyguard/action";
Lucas Dupin957e50c2017-10-10 11:23:27 -070087
Lucas Dupin1f7374a2018-02-26 18:08:33 -080088 /**
89 * Only show alarms that will ring within N hours.
90 */
91 @VisibleForTesting
92 static final int ALARM_VISIBILITY_HOURS = 12;
93
Lucas Dupina0d51192018-08-15 11:29:32 -070094 private static KeyguardSliceProvider sInstance;
95
Lucas Dupin957e50c2017-10-10 11:23:27 -070096 protected final Uri mSliceUri;
Lucas Dupine570af62019-02-10 14:52:30 -080097 protected final Uri mHeaderUri;
Lucas Dupin6bd86012017-12-05 17:58:57 -080098 protected final Uri mDateUri;
Lucas Dupin9cc40de2017-12-19 16:09:32 -080099 protected final Uri mAlarmUri;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700100 protected final Uri mDndUri;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800101 protected final Uri mMediaUri;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700102 private final Date mCurrentTime = new Date();
Lucas Dupin957e50c2017-10-10 11:23:27 -0700103 private final Handler mHandler;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700104 private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm;
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700105 private final HashSet<Integer> mMediaInvisibleStates;
Lucas Dupin9027bda2019-05-28 14:34:47 -0700106 private final Object mMediaToken = new Object();
Lucas Dupinba914792019-06-20 14:07:57 -0700107 @VisibleForTesting
108 protected SettableWakeLock mMediaWakeLock;
109 @VisibleForTesting
110 protected ZenModeController mZenModeController;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700111 private String mDatePattern;
112 private DateFormat mDateFormat;
113 private String mLastText;
114 private boolean mRegistered;
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800115 private String mNextAlarm;
116 private NextAlarmController mNextAlarmController;
Lucas Dupinba914792019-06-20 14:07:57 -0700117 @VisibleForTesting
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800118 protected AlarmManager mAlarmManager;
Lucas Dupinba914792019-06-20 14:07:57 -0700119 @VisibleForTesting
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800120 protected ContentResolver mContentResolver;
121 private AlarmManager.AlarmClockInfo mNextAlarmInfo;
Lucas Dupina0d51192018-08-15 11:29:32 -0700122 private PendingIntent mPendingIntent;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800123 protected NotificationMediaManager mMediaManager;
Lucas Dupin3d560e42019-01-07 13:52:44 -0800124 private StatusBarStateController mStatusBarStateController;
Lucas Dupin9027bda2019-05-28 14:34:47 -0700125 private CharSequence mMediaTitle;
126 private CharSequence mMediaArtist;
Lucas Dupin3d560e42019-01-07 13:52:44 -0800127 protected boolean mDozing;
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700128 private boolean mMediaIsVisible;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700129
130 /**
131 * Receiver responsible for time ticking and updating the date format.
132 */
133 @VisibleForTesting
134 final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
135 @Override
136 public void onReceive(Context context, Intent intent) {
137 final String action = intent.getAction();
Lucas Dupin7e171e22018-12-20 11:29:35 -0800138 if (Intent.ACTION_DATE_CHANGED.equals(action)) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800139 synchronized (this) {
140 updateClockLocked();
141 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800142 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800143 synchronized (this) {
144 cleanDateFormatLocked();
145 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700146 }
147 }
148 };
149
Lucas Dupin7e171e22018-12-20 11:29:35 -0800150 @VisibleForTesting
151 final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
152 new KeyguardUpdateMonitorCallback() {
153 @Override
154 public void onTimeChanged() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800155 synchronized (this) {
156 updateClockLocked();
157 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800158 }
159
160 @Override
161 public void onTimeZoneChanged(TimeZone timeZone) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800162 synchronized (this) {
163 cleanDateFormatLocked();
164 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800165 }
166 };
167
Lucas Dupin957e50c2017-10-10 11:23:27 -0700168 public KeyguardSliceProvider() {
169 this(new Handler());
170 }
171
Lucas Dupina0d51192018-08-15 11:29:32 -0700172 public static KeyguardSliceProvider getAttachedInstance() {
173 return KeyguardSliceProvider.sInstance;
174 }
175
Lucas Dupin957e50c2017-10-10 11:23:27 -0700176 @VisibleForTesting
177 KeyguardSliceProvider(Handler handler) {
178 mHandler = handler;
179 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 Dupin6b40d5e2019-05-15 19:47:11 -0700185
186 mMediaInvisibleStates = new HashSet<>();
187 mMediaInvisibleStates.add(PlaybackState.STATE_NONE);
188 mMediaInvisibleStates.add(PlaybackState.STATE_STOPPED);
189 mMediaInvisibleStates.add(PlaybackState.STATE_PAUSED);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700190 }
191
Lucas Dupin3d560e42019-01-07 13:52:44 -0800192 /**
193 * Initialize dependencies that don't exist during {@link android.content.ContentProvider}
194 * instantiation.
195 *
196 * @param mediaManager {@link NotificationMediaManager} singleton.
197 * @param statusBarStateController {@link StatusBarStateController} singleton.
198 */
199 public void initDependencies(
200 NotificationMediaManager mediaManager,
201 StatusBarStateController statusBarStateController) {
202 mMediaManager = mediaManager;
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800203 mMediaManager.addCallback(this);
Lucas Dupin3d560e42019-01-07 13:52:44 -0800204 mStatusBarStateController = statusBarStateController;
205 mStatusBarStateController.addCallback(this);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800206 }
207
208 @AnyThread
Lucas Dupin957e50c2017-10-10 11:23:27 -0700209 @Override
210 public Slice onBindSlice(Uri sliceUri) {
Lucas Dupina0d51192018-08-15 11:29:32 -0700211 Trace.beginSection("KeyguardSliceProvider#onBindSlice");
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800212 Slice slice;
213 synchronized (this) {
214 ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
Lucas Dupin3d560e42019-01-07 13:52:44 -0800215 if (needsMediaLocked()) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800216 addMediaLocked(builder);
217 } else {
218 builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800219 }
Lucas Dupinc9548242019-04-11 10:59:13 -0700220 addNextAlarmLocked(builder);
221 addZenModeLocked(builder);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800222 addPrimaryActionLocked(builder);
223 slice = builder.build();
224 }
Lucas Dupina0d51192018-08-15 11:29:32 -0700225 Trace.endSection();
226 return slice;
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800227 }
228
Lucas Dupin3d560e42019-01-07 13:52:44 -0800229 protected boolean needsMediaLocked() {
Lucas Dupin9027bda2019-05-28 14:34:47 -0700230 return !TextUtils.isEmpty(mMediaTitle) && mMediaIsVisible && mDozing;
Lucas Dupin3d560e42019-01-07 13:52:44 -0800231 }
232
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800233 protected void addMediaLocked(ListBuilder listBuilder) {
Lucas Dupin9027bda2019-05-28 14:34:47 -0700234 if (TextUtils.isEmpty(mMediaTitle)) {
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700235 return;
236 }
Lucas Dupin9027bda2019-05-28 14:34:47 -0700237 listBuilder.setHeader(new ListBuilder.HeaderBuilder(mHeaderUri).setTitle(mMediaTitle));
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700238
Lucas Dupin9027bda2019-05-28 14:34:47 -0700239 if (!TextUtils.isEmpty(mMediaArtist)) {
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700240 RowBuilder albumBuilder = new RowBuilder(mMediaUri);
Lucas Dupin9027bda2019-05-28 14:34:47 -0700241 albumBuilder.setTitle(mMediaArtist);
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700242
243 Icon mediaIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon();
244 IconCompat mediaIconCompat = mediaIcon == null ? null
245 : IconCompat.createFromIcon(getContext(), mediaIcon);
246 if (mediaIconCompat != null) {
247 albumBuilder.addEndItem(mediaIconCompat, ListBuilder.ICON_IMAGE);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800248 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800249
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700250 listBuilder.addRow(albumBuilder);
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800251 }
252 }
253
254 protected void addPrimaryActionLocked(ListBuilder builder) {
Mady Mellor78f04ef2018-05-23 15:04:47 -0700255 // Add simple action because API requires it; Keyguard handles presenting
256 // its own slices so this action + icon are actually never used.
Jason Monk1045e0b2018-08-06 09:42:10 -0400257 IconCompat icon = IconCompat.createWithResource(getContext(),
258 R.drawable.ic_access_alarms_big);
Lucas Dupina0d51192018-08-15 11:29:32 -0700259 SliceAction action = SliceAction.createDeeplink(mPendingIntent, icon,
Jason Monk1045e0b2018-08-06 09:42:10 -0400260 ListBuilder.ICON_IMAGE, mLastText);
Jason Monk1045e0b2018-08-06 09:42:10 -0400261 RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI))
262 .setPrimaryAction(action);
Mady Mellor78f04ef2018-05-23 15:04:47 -0700263 builder.addRow(primaryActionRow);
264 }
265
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800266 protected void addNextAlarmLocked(ListBuilder builder) {
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800267 if (TextUtils.isEmpty(mNextAlarm)) {
268 return;
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800269 }
Jason Monk1045e0b2018-08-06 09:42:10 -0400270 IconCompat alarmIcon = IconCompat.createWithResource(getContext(),
271 R.drawable.ic_access_alarms_big);
272 RowBuilder alarmRowBuilder = new RowBuilder(mAlarmUri)
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800273 .setTitle(mNextAlarm)
Jason Monk1045e0b2018-08-06 09:42:10 -0400274 .addEndItem(alarmIcon, ListBuilder.ICON_IMAGE);
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800275 builder.addRow(alarmRowBuilder);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700276 }
277
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700278 /**
279 * Add zen mode (DND) icon to slice if it's enabled.
280 * @param builder The slice builder.
281 */
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800282 protected void addZenModeLocked(ListBuilder builder) {
Lucas Dupin627ad372018-11-27 10:28:25 +0100283 if (!isDndOn()) {
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700284 return;
285 }
Jason Monk1045e0b2018-08-06 09:42:10 -0400286 RowBuilder dndBuilder = new RowBuilder(mDndUri)
Lucas Dupin1d5e9102018-05-21 17:58:05 -0700287 .setContentDescription(getContext().getResources()
288 .getString(R.string.accessibility_quick_settings_dnd))
Amin Shaikhad29e932019-03-08 11:26:12 -0500289 .addEndItem(
290 IconCompat.createWithResource(getContext(), R.drawable.stat_sys_dnd),
291 ListBuilder.ICON_IMAGE);
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700292 builder.addRow(dndBuilder);
293 }
294
295 /**
Lucas Dupin627ad372018-11-27 10:28:25 +0100296 * Return true if DND is enabled.
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700297 */
Lucas Dupin627ad372018-11-27 10:28:25 +0100298 protected boolean isDndOn() {
299 return mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700300 }
301
Lucas Dupin957e50c2017-10-10 11:23:27 -0700302 @Override
Lucas Dupin6bd86012017-12-05 17:58:57 -0800303 public boolean onCreateSliceProvider() {
Lucas Dupinba914792019-06-20 14:07:57 -0700304 synchronized (this) {
305 KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance;
306 if (oldInstance != null) {
307 oldInstance.onDestroy();
308 }
309
310 mAlarmManager = getContext().getSystemService(AlarmManager.class);
311 mContentResolver = getContext().getContentResolver();
312 mNextAlarmController = new NextAlarmControllerImpl(getContext());
313 mNextAlarmController.addCallback(this);
314 mZenModeController = new ZenModeControllerImpl(getContext(), mHandler);
315 mZenModeController.addCallback(this);
316 mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
317 mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
318 mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
319 "media");
320 KeyguardSliceProvider.sInstance = this;
321 registerClockUpdate();
322 updateClockLocked();
323 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700324 return true;
325 }
326
Lucas Dupinba914792019-06-20 14:07:57 -0700327 @VisibleForTesting
328 protected void onDestroy() {
329 synchronized (this) {
330 mNextAlarmController.removeCallback(this);
331 mZenModeController.removeCallback(this);
332 mMediaWakeLock.setAcquired(false);
333 mAlarmManager.cancel(mUpdateNextAlarm);
334 if (mRegistered) {
335 mRegistered = false;
336 getKeyguardUpdateMonitor().removeCallback(mKeyguardUpdateMonitorCallback);
337 getContext().unregisterReceiver(mIntentReceiver);
338 }
339 }
340 }
341
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700342 @Override
343 public void onZenChanged(int zen) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800344 notifyChange();
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700345 }
346
347 @Override
348 public void onConfigChanged(ZenModeConfig config) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800349 notifyChange();
Lucas Dupin8b77a3b2018-05-01 13:23:03 -0700350 }
351
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800352 private void updateNextAlarm() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800353 synchronized (this) {
354 if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
355 String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
356 ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm";
357 mNextAlarm = android.text.format.DateFormat.format(pattern,
358 mNextAlarmInfo.getTriggerTime()).toString();
359 } else {
360 mNextAlarm = "";
361 }
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800362 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800363 notifyChange();
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800364 }
365
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800366 private boolean withinNHoursLocked(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) {
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800367 if (alarmClockInfo == null) {
368 return false;
369 }
370
371 long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours);
372 return mNextAlarmInfo.getTriggerTime() <= limit;
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800373 }
374
Lucas Dupin6bd86012017-12-05 17:58:57 -0800375 /**
376 * Registers a broadcast receiver for clock updates, include date, time zone and manually
377 * changing the date/time via the settings app.
Lucas Dupin6bd86012017-12-05 17:58:57 -0800378 */
Lucas Dupinba914792019-06-20 14:07:57 -0700379 @VisibleForTesting
380 protected void registerClockUpdate() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800381 synchronized (this) {
382 if (mRegistered) {
383 return;
384 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700385
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800386 IntentFilter filter = new IntentFilter();
387 filter.addAction(Intent.ACTION_DATE_CHANGED);
388 filter.addAction(Intent.ACTION_LOCALE_CHANGED);
389 getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
390 null /* scheduler */);
391 getKeyguardUpdateMonitor().registerCallback(mKeyguardUpdateMonitorCallback);
392 mRegistered = true;
393 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700394 }
395
396 @VisibleForTesting
397 boolean isRegistered() {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800398 synchronized (this) {
399 return mRegistered;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700400 }
401 }
402
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800403 protected void updateClockLocked() {
404 final String text = getFormattedDateLocked();
405 if (!text.equals(mLastText)) {
406 mLastText = text;
407 notifyChange();
408 }
409 }
410
411 protected String getFormattedDateLocked() {
Lucas Dupin957e50c2017-10-10 11:23:27 -0700412 if (mDateFormat == null) {
413 final Locale l = Locale.getDefault();
414 DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
415 format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
416 mDateFormat = format;
417 }
418 mCurrentTime.setTime(System.currentTimeMillis());
419 return mDateFormat.format(mCurrentTime);
420 }
421
422 @VisibleForTesting
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800423 void cleanDateFormatLocked() {
Lucas Dupin957e50c2017-10-10 11:23:27 -0700424 mDateFormat = null;
425 }
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800426
427 @Override
428 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800429 synchronized (this) {
430 mNextAlarmInfo = nextAlarm;
431 mAlarmManager.cancel(mUpdateNextAlarm);
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800432
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800433 long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime()
434 - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS);
435 if (triggerAt > 0) {
436 mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm",
437 mUpdateNextAlarm, mHandler);
438 }
Lucas Dupin1f7374a2018-02-26 18:08:33 -0800439 }
440 updateNextAlarm();
Lucas Dupin9cc40de2017-12-19 16:09:32 -0800441 }
Lucas Dupin7e171e22018-12-20 11:29:35 -0800442
443 @VisibleForTesting
444 protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
445 return KeyguardUpdateMonitor.getInstance(getContext());
446 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800447
Lucas Dupin76527ee2019-02-20 10:46:23 -0800448 /**
449 * Called whenever new media metadata is available.
450 * @param metadata New metadata.
451 */
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800452 @Override
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700453 public void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800454 synchronized (this) {
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700455 boolean nextVisible = !mMediaInvisibleStates.contains(state);
Lucas Dupin9027bda2019-05-28 14:34:47 -0700456 mHandler.removeCallbacksAndMessages(mMediaToken);
457 if (mMediaIsVisible && !nextVisible) {
458 // We need to delay this event for a few millis when stopping to avoid jank in the
459 // animation. The media app might not send its update when buffering, and the slice
460 // would end up without a header for 0.5 second.
461 mMediaWakeLock.setAcquired(true);
462 mHandler.postDelayed(() -> {
463 updateMediaStateLocked(metadata, state);
464 mMediaWakeLock.setAcquired(false);
465 }, mMediaToken, 2000);
466 } else {
467 mMediaWakeLock.setAcquired(false);
468 updateMediaStateLocked(metadata, state);
Lucas Dupin6b40d5e2019-05-15 19:47:11 -0700469 }
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800470 }
Lucas Dupin9027bda2019-05-28 14:34:47 -0700471 }
472
473 private void updateMediaStateLocked(MediaMetadata metadata, @PlaybackState.State int state) {
474 boolean nextVisible = !mMediaInvisibleStates.contains(state);
475 CharSequence title = null;
476 if (metadata != null) {
477 title = metadata.getText(MediaMetadata.METADATA_KEY_TITLE);
478 if (TextUtils.isEmpty(title)) {
479 title = getContext().getResources().getString(R.string.music_controls_no_title);
480 }
481 }
482 CharSequence artist = metadata == null ? null : metadata.getText(
483 MediaMetadata.METADATA_KEY_ARTIST);
484
485 if (nextVisible == mMediaIsVisible && TextUtils.equals(title, mMediaTitle)
486 && TextUtils.equals(artist, mMediaArtist)) {
487 return;
488 }
489 mMediaTitle = title;
490 mMediaArtist = artist;
491 mMediaIsVisible = nextVisible;
Lucas Dupin76527ee2019-02-20 10:46:23 -0800492 notifyChange();
Lucas Dupin6a03a9f2018-12-20 17:13:52 -0800493 }
494
495 protected void notifyChange() {
496 mContentResolver.notifyChange(mSliceUri, null /* observer */);
497 }
Lucas Dupin3d560e42019-01-07 13:52:44 -0800498
499 @Override
500 public void onDozingChanged(boolean isDozing) {
501 final boolean notify;
502 synchronized (this) {
503 boolean neededMedia = needsMediaLocked();
504 mDozing = isDozing;
505 notify = neededMedia != needsMediaLocked();
506 }
507 if (notify) {
508 notifyChange();
509 }
510 }
511
512 @Override
513 public void onStateChanged(int newState) {
514 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700515}