2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.keyguard;
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;
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;
44 import java.util.Date;
45 import java.util.Locale;
46 import java.util.concurrent.TimeUnit;
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;
55 * Simple Slice provider that shows the current date.
57 public class KeyguardSliceProvider extends SliceProvider implements
58 NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback {
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";
69 * Only show alarms that will ring within N hours.
72 static final int ALARM_VISIBILITY_HOURS = 12;
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;
93 * Receiver responsible for time ticking and updating the date format.
96 final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
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);
110 mHandler.post(KeyguardSliceProvider.this::updateClock);
115 public KeyguardSliceProvider() {
120 KeyguardSliceProvider(Handler 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);
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);
134 addPrimaryAction(builder);
135 return builder.build();
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);
146 RowBuilder primaryActionRow = new RowBuilder(builder, Uri.parse(KEYGUARD_ACTION_URI))
147 .setPrimaryAction(action);
148 builder.addRow(primaryActionRow);
151 protected void addNextAlarm(ListBuilder builder) {
152 if (TextUtils.isEmpty(mNextAlarm)) {
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);
164 * Add zen mode (DND) icon to slice if it's enabled.
165 * @param builder The slice builder.
167 protected void addZenMode(ListBuilder builder) {
168 if (!isDndSuppressingNotifications()) {
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);
179 * Return true if DND is enabled suppressing notifications.
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;
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();
203 public void onZenChanged(int zen) {
204 mContentResolver.notifyChange(mSliceUri, null /* observer */);
208 public void onConfigChanged(ZenModeConfig config) {
209 mContentResolver.notifyChange(mSliceUri, null /* observer */);
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();
221 mContentResolver.notifyChange(mSliceUri, null /* observer */);
224 private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) {
225 if (alarmClockInfo == null) {
229 long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours);
230 return mNextAlarmInfo.getTriggerTime() <= limit;
234 * Registers a broadcast receiver for clock updates, include date, time zone and manually
235 * changing the date/time via the settings app.
237 private void registerClockUpdate() {
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 */);
253 boolean isRegistered() {
257 protected void updateClock() {
258 final String text = getFormattedDate();
259 if (!text.equals(mLastText)) {
261 mContentResolver.notifyChange(mSliceUri, null /* observer */);
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;
272 mCurrentTime.setTime(System.currentTimeMillis());
273 return mDateFormat.format(mCurrentTime);
277 void cleanDateFormat() {
282 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
283 mNextAlarmInfo = nextAlarm;
284 mAlarmManager.cancel(mUpdateNextAlarm);
286 long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime()
287 - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS);
289 mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm",
290 mUpdateNextAlarm, mHandler);