Show next alarm on ambient display
Next alarm will be visible 12h before triggering.
Test: Set alarm that will ring in 8h
Test: Set alarm that will ring in 14h
Test: Set alarm that will ring in 11:59, wait one minute
Test: atest packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
Change-Id: Icd4253771efcdf5afb4e9e52329fa410d7fd1cc1
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index b54d09a..2adb286 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -167,7 +167,8 @@
}
mClickActions.put(button, pendingIntent);
- button.setText(rc.getTitleItem().getText());
+ final SliceItem titleItem = rc.getTitleItem();
+ button.setText(titleItem == null ? null : titleItem.getText());
Drawable iconDrawable = null;
SliceItem icon = SliceQuery.find(item.getSlice(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index c7d276c..26618bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -27,6 +28,7 @@
import android.icu.text.DisplayContext;
import android.net.Uri;
import android.os.Handler;
+import android.os.SystemClock;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -36,6 +38,7 @@
import java.util.Date;
import java.util.Locale;
+import java.util.concurrent.TimeUnit;
import androidx.app.slice.Slice;
import androidx.app.slice.SliceProvider;
@@ -53,6 +56,12 @@
public static final String KEYGUARD_NEXT_ALARM_URI =
"content://com.android.systemui.keyguard/alarm";
+ /**
+ * Only show alarms that will ring within N hours.
+ */
+ @VisibleForTesting
+ static final int ALARM_VISIBILITY_HOURS = 12;
+
private final Date mCurrentTime = new Date();
protected final Uri mSliceUri;
protected final Uri mDateUri;
@@ -65,6 +74,10 @@
private boolean mRegisteredEveryMinute;
private String mNextAlarm;
private NextAlarmController mNextAlarmController;
+ protected AlarmManager mAlarmManager;
+ protected ContentResolver mContentResolver;
+ private AlarmManager.AlarmClockInfo mNextAlarmInfo;
+ private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -105,17 +118,26 @@
public Slice onBindSlice(Uri sliceUri) {
ListBuilder builder = new ListBuilder(getContext(), mSliceUri);
builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText));
- if (!TextUtils.isEmpty(mNextAlarm)) {
- Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
- builder.addRow(new RowBuilder(builder, mAlarmUri)
- .setTitle(mNextAlarm).addEndItem(icon));
+ addNextAlarm(builder);
+ return builder.build();
+ }
+
+ protected void addNextAlarm(ListBuilder builder) {
+ if (TextUtils.isEmpty(mNextAlarm)) {
+ return;
}
- return builder.build();
+ Icon alarmIcon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
+ RowBuilder alarmRowBuilder = new RowBuilder(builder, mAlarmUri)
+ .setTitle(mNextAlarm)
+ .addEndItem(alarmIcon);
+ builder.addRow(alarmRowBuilder);
}
@Override
public boolean onCreateSliceProvider() {
+ mAlarmManager = getContext().getSystemService(AlarmManager.class);
+ mContentResolver = getContext().getContentResolver();
mNextAlarmController = new NextAlarmControllerImpl(getContext());
mNextAlarmController.addCallback(this);
mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
@@ -124,15 +146,25 @@
return true;
}
- public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
- if (info == null) {
- return "";
+ private void updateNextAlarm() {
+ if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
+ String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
+ ActivityManager.getCurrentUser()) ? "H:mm" : "h:mm";
+ mNextAlarm = android.text.format.DateFormat.format(pattern,
+ mNextAlarmInfo.getTriggerTime()).toString();
+ } else {
+ mNextAlarm = "";
}
- String skeleton = android.text.format.DateFormat
- .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
- String pattern = android.text.format.DateFormat
- .getBestDateTimePattern(Locale.getDefault(), skeleton);
- return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+ mContentResolver.notifyChange(mSliceUri, null /* observer */);
+ }
+
+ private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) {
+ if (alarmClockInfo == null) {
+ return false;
+ }
+
+ long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours);
+ return mNextAlarmInfo.getTriggerTime() <= limit;
}
/**
@@ -181,7 +213,7 @@
final String text = getFormattedDate();
if (!text.equals(mLastText)) {
mLastText = text;
- getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ mContentResolver.notifyChange(mSliceUri, null /* observer */);
}
}
@@ -203,7 +235,15 @@
@Override
public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarm = formatNextAlarm(getContext(), nextAlarm);
- getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ mNextAlarmInfo = nextAlarm;
+ mAlarmManager.cancel(mUpdateNextAlarm);
+
+ long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime()
+ - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS);
+ if (triggerAt > 0) {
+ mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm",
+ mUpdateNextAlarm, mHandler);
+ }
+ updateNextAlarm();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 78481d3..2151436 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -15,10 +15,10 @@
package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
@@ -51,6 +51,8 @@
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.policy.NextAlarmController;
+import java.util.Locale;
+
/**
* View that contains the top-most bits of the screen (primarily the status bar with date, time, and
* battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
@@ -289,7 +291,7 @@
@Override
public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarmText = nextAlarm != null ? formatNextAlarm(mContext, nextAlarm) : null;
+ mNextAlarmText = nextAlarm != null ? formatNextAlarm(nextAlarm) : null;
if (mNextAlarmText != null) {
hideLongPressTooltip(true /* shouldFadeInAlarmText */);
} else {
@@ -430,4 +432,15 @@
public void setCallback(Callback qsPanelCallback) {
mHeaderQsPanel.setCallback(qsPanelCallback);
}
+
+ private String formatNextAlarm(AlarmManager.AlarmClockInfo info) {
+ if (info == null) {
+ return "";
+ }
+ String skeleton = android.text.format.DateFormat
+ .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ String pattern = android.text.format.DateFormat
+ .getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index cd409d8..b6116e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -16,10 +16,17 @@
package com.android.systemui.keyguard;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
import androidx.app.slice.Slice;
+
+import android.app.AlarmManager;
+import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
-import android.os.Debug;
import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -32,24 +39,31 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
import androidx.app.slice.SliceItem;
import androidx.app.slice.SliceProvider;
import androidx.app.slice.SliceSpecs;
import androidx.app.slice.core.SliceQuery;
-import androidx.app.slice.widget.SliceLiveData;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
public class KeyguardSliceProviderTest extends SysuiTestCase {
+ @Mock
+ private ContentResolver mContentResolver;
+ @Mock
+ private AlarmManager mAlarmManager;
private TestableKeyguardSliceProvider mProvider;
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
mProvider = new TestableKeyguardSliceProvider();
mProvider.attachInfo(getContext(), null);
SliceProvider.setSpecs(Arrays.asList(SliceSpecs.LIST));
@@ -70,7 +84,7 @@
@Test
public void returnsValidSlice() {
- Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
+ Slice slice = mProvider.onBindSlice(mProvider.getUri());
SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
android.app.slice.Slice.HINT_TITLE,
null /* nonHints */);
@@ -87,21 +101,52 @@
@Test
public void updatesClock() {
- mProvider.mUpdateClockInvokations = 0;
mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
TestableLooper.get(this).processAllMessages();
- Assert.assertEquals("Clock should have been updated.", 1 /* expected */,
- mProvider.mUpdateClockInvokations);
+ verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
+ }
+
+ @Test
+ public void schedulesAlarm12hBefore() {
+ long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(16);
+ AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null);
+ mProvider.onNextAlarmChanged(alarmClockInfo);
+
+ long twelveHours = TimeUnit.HOURS.toMillis(KeyguardSliceProvider.ALARM_VISIBILITY_HOURS);
+ long triggerAt = in16Hours - twelveHours;
+ verify(mAlarmManager).setExact(eq(AlarmManager.RTC), eq(triggerAt), anyString(), any(),
+ any());
+ }
+
+ @Test
+ public void updatingNextAlarmInvalidatesSlice() {
+ long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(8);
+ AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null);
+ mProvider.onNextAlarmChanged(alarmClockInfo);
+
+ verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
}
private class TestableKeyguardSliceProvider extends KeyguardSliceProvider {
int mCleanDateFormatInvokations;
- int mUpdateClockInvokations;
+ private int mCounter;
TestableKeyguardSliceProvider() {
super(new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()));
}
+ Uri getUri() {
+ return mSliceUri;
+ }
+
+ @Override
+ public boolean onCreateSliceProvider() {
+ super.onCreateSliceProvider();
+ mAlarmManager = KeyguardSliceProviderTest.this.mAlarmManager;
+ mContentResolver = KeyguardSliceProviderTest.this.mContentResolver;
+ return true;
+ }
+
@Override
void cleanDateFormat() {
super.cleanDateFormat();
@@ -109,9 +154,8 @@
}
@Override
- protected void updateClock() {
- super.updateClock();
- mUpdateClockInvokations++;
+ protected String getFormattedDate() {
+ return super.getFormattedDate() + mCounter++;
}
}