diff options
7 files changed, 805 insertions, 59 deletions
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 735b82238227..1ec24061eb98 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -890,7 +890,17 @@ public class ZenModeConfig implements Parcelable { } public static boolean isValidScheduleConditionId(Uri conditionId) { - return tryParseScheduleConditionId(conditionId) != null; + ScheduleInfo info; + try { + info = tryParseScheduleConditionId(conditionId); + } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { + return false; + } + + if (info == null || info.days == null || info.days.length == 0) { + return false; + } + return true; } public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) { diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index fac254a0083a..863f17b432f2 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<com.android.systemui.HardwareUiLayout +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" @@ -45,4 +45,4 @@ </LinearLayout> </RelativeLayout> -</com.android.systemui.HardwareUiLayout> +</RelativeLayout> diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 0d41e2029086..ca23980472b6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -19,6 +19,9 @@ package com.android.systemui.volume; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE; import android.accessibilityservice.AccessibilityServiceInfo; @@ -42,6 +45,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.provider.Settings; import android.provider.Settings.Global; import android.util.Log; import android.util.Slog; @@ -67,6 +71,7 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.HardwareBgDrawable; import com.android.systemui.HardwareUiLayout; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -74,6 +79,7 @@ import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; +import com.android.systemui.util.leak.RotationUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -97,9 +103,13 @@ public class VolumeDialogImpl implements VolumeDialog { private final VolumeDialogController mController; private Window mWindow; - private HardwareUiLayout mHardwareLayout; + //private HardwareUiLayout mHardwareLayout; private CustomDialog mDialog; private ViewGroup mDialogView; + private boolean mEdgeBleed; + private boolean mRoundedDivider; + private HardwareBgDrawable mBackground; + private int mRotation = ROTATION_NONE; private ViewGroup mDialogRowsView; private ViewGroup mDialogContentView; private final List<VolumeRow> mRows = new ArrayList<>(); @@ -111,6 +121,8 @@ public class VolumeDialogImpl implements VolumeDialog { private final Accessibility mAccessibility = new Accessibility(); private final ColorStateList mActiveSliderTint; private final ColorStateList mInactiveSliderTint; + private static final String EDGE_BLEED = "sysui_hwui_edge_bleed"; + private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider"; private boolean mShowing; private boolean mShowA11yStream; @@ -181,8 +193,16 @@ public class VolumeDialogImpl implements VolumeDialog { return true; } }); - mHardwareLayout = HardwareUiLayout.get(mDialogView); - mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE)); + + mEdgeBleed = Settings.Secure.getInt(mContext.getContentResolver(), + EDGE_BLEED, 0) != 0; + mRoundedDivider = Settings.Secure.getInt(mContext.getContentResolver(), + ROUNDED_DIVIDER, 1) != 0; + updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); + mBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, mContext); + mDialogView.setBackground(mBackground); + //mHardwareLayout = HardwareUiLayout.get(mDialogView); + //mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE)); mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content); mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows); @@ -210,6 +230,25 @@ public class VolumeDialogImpl implements VolumeDialog { updateRowsH(getActiveRow()); } + private int getEdgePadding() { + return mContext.getResources().getDimensionPixelSize(R.dimen.edge_margin); + } + + private void updateEdgeMargin(int edge) { + if (mDialogView != null) { + mRotation = RotationUtils.getRotation(mContext); + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mDialogView.getLayoutParams(); + if (mRotation == ROTATION_LANDSCAPE) { + params.topMargin = edge; + } else if (mRotation == ROTATION_SEASCAPE) { + params.bottomMargin = edge; + } else { + params.rightMargin = edge; + } + mDialogView.setLayoutParams(params); + } + } + private ColorStateList loadColorStateList(int colorResId) { return ColorStateList.valueOf(mContext.getColor(colorResId)); } @@ -389,11 +428,11 @@ public class VolumeDialogImpl implements VolumeDialog { rescheduleTimeoutH(); if (mShowing) return; mShowing = true; - mHardwareLayout.setTranslationX(getAnimTranslation()); - mHardwareLayout.setAlpha(0); - mHardwareLayout.animate() + mDialogView.setTranslationY(getAnimTranslation()); + mDialogView.setAlpha(0); + mDialogView.animate() .alpha(1) - .translationX(0) + .translationY(0) .setDuration(300) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .withEndAction(() -> { @@ -432,9 +471,9 @@ public class VolumeDialogImpl implements VolumeDialog { mHandler.removeMessages(H.SHOW); if (!mShowing) return; mShowing = false; - mHardwareLayout.setTranslationX(0); - mHardwareLayout.setAlpha(1); - mHardwareLayout.animate() + mDialogView.setTranslationX(0); + mDialogView.setAlpha(1); + mDialogView.animate() .alpha(0) .translationX(getAnimTranslation()) .setDuration(300) diff --git a/services/core/java/com/android/server/notification/ScheduleCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java index 40230bd2ba82..5ff0e21078af 100644 --- a/services/core/java/com/android/server/notification/ScheduleCalendar.java +++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java @@ -18,6 +18,7 @@ package com.android.server.notification; import android.service.notification.ZenModeConfig.ScheduleInfo; import android.util.ArraySet; +import android.util.Log; import java.util.Calendar; import java.util.Objects; @@ -41,10 +42,25 @@ public class ScheduleCalendar { } public void maybeSetNextAlarm(long now, long nextAlarm) { - if (mSchedule != null) { - if (mSchedule.exitAtAlarm - && (now > mSchedule.nextAlarm || nextAlarm < mSchedule.nextAlarm)) { - mSchedule.nextAlarm = nextAlarm; + if (mSchedule != null && mSchedule.exitAtAlarm) { + // alarm canceled + if (nextAlarm == 0) { + mSchedule.nextAlarm = 0; + } + // only allow alarms in the future + if (nextAlarm > now) { + // store earliest alarm + if (mSchedule.nextAlarm == 0) { + mSchedule.nextAlarm = nextAlarm; + } else { + mSchedule.nextAlarm = Math.min(mSchedule.nextAlarm, nextAlarm); + } + } else if (mSchedule.nextAlarm < now) { + if (ScheduleConditionProvider.DEBUG) { + Log.d(ScheduleConditionProvider.TAG, + "All alarms are in the past " + mSchedule.nextAlarm); + } + mSchedule.nextAlarm = 0; } } } @@ -87,6 +103,9 @@ public class ScheduleCalendar { } public boolean shouldExitForAlarm(long time) { + if (mSchedule == null) { + return false; + } return mSchedule.exitAtAlarm && mSchedule.nextAlarm != 0 && time >= mSchedule.nextAlarm; diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java index 50a51b297aa9..c5f80bb63d2d 100644 --- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java +++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java @@ -37,6 +37,8 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; @@ -62,10 +64,9 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private static final String SEPARATOR = ";"; private static final String SCP_SETTING = "snoozed_schedule_condition_provider"; - private final Context mContext = this; private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>(); - private ArraySet<Uri> mSnoozed = new ArraySet<>(); + private ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>(); private AlarmManager mAlarmManager; private boolean mConnected; @@ -102,7 +103,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { pw.println(mSubscriptions.get(conditionId).toString()); } } - pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozed)); + pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozedForAlarm)); dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now); } @@ -129,7 +130,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { public void onSubscribe(Uri conditionId) { if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) { - notifyCondition(createCondition(conditionId, Condition.STATE_FALSE, "badCondition")); + notifyCondition(createCondition(conditionId, Condition.STATE_ERROR, "invalidId")); return; } synchronized (mSubscriptions) { @@ -169,32 +170,11 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { synchronized (mSubscriptions) { setRegistered(!mSubscriptions.isEmpty()); for (Uri conditionId : mSubscriptions.keySet()) { - final ScheduleCalendar cal = mSubscriptions.get(conditionId); - if (cal != null && cal.isInSchedule(now)) { - if (conditionSnoozed(conditionId) || cal.shouldExitForAlarm(now)) { - conditionsToNotify.add(createCondition( - conditionId, Condition.STATE_FALSE, "alarmCanceled")); - addSnoozed(conditionId); - } else { - conditionsToNotify.add(createCondition( - conditionId, Condition.STATE_TRUE, "meetsSchedule")); - } - cal.maybeSetNextAlarm(now, nextUserAlarmTime); - } else { - conditionsToNotify.add(createCondition( - conditionId, Condition.STATE_FALSE, "!meetsSchedule")); - removeSnoozed(conditionId); - if (cal != null && nextUserAlarmTime == 0) { - cal.maybeSetNextAlarm(now, nextUserAlarmTime); - } - } - if (cal != null) { - final long nextChangeTime = cal.getNextChangeTime(now); - if (nextChangeTime > 0 && nextChangeTime > now) { - if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) { - mNextAlarmTime = nextChangeTime; - } - } + Condition condition = + evaluateSubscriptionLocked(conditionId, mSubscriptions.get(conditionId), + now, nextUserAlarmTime); + if (condition != null) { + conditionsToNotify.add(condition); } } } @@ -202,6 +182,39 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { updateAlarm(now, mNextAlarmTime); } + @VisibleForTesting + @GuardedBy("mSubscriptions") + Condition evaluateSubscriptionLocked(Uri conditionId, ScheduleCalendar cal, + long now, long nextUserAlarmTime) { + Condition condition; + if (cal == null) { + condition = createCondition(conditionId, Condition.STATE_ERROR, "!invalidId"); + removeSnoozed(conditionId); + return condition; + } + if (cal.isInSchedule(now)) { + if (conditionSnoozed(conditionId)) { + condition = createCondition(conditionId, Condition.STATE_FALSE, "snoozed"); + } else if (cal.shouldExitForAlarm(now)) { + condition = createCondition(conditionId, Condition.STATE_FALSE, "alarmCanceled"); + addSnoozed(conditionId); + } else { + condition = createCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule"); + } + } else { + condition = createCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule"); + removeSnoozed(conditionId); + } + cal.maybeSetNextAlarm(now, nextUserAlarmTime); + final long nextChangeTime = cal.getNextChangeTime(now); + if (nextChangeTime > 0 && nextChangeTime > now) { + if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) { + mNextAlarmTime = nextChangeTime; + } + } + return condition; + } + private void updateAlarm(long now, long time) { final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, @@ -266,27 +279,28 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } private boolean conditionSnoozed(Uri conditionId) { - synchronized (mSnoozed) { - return mSnoozed.contains(conditionId); + synchronized (mSnoozedForAlarm) { + return mSnoozedForAlarm.contains(conditionId); } } - private void addSnoozed(Uri conditionId) { - synchronized (mSnoozed) { - mSnoozed.add(conditionId); + @VisibleForTesting + void addSnoozed(Uri conditionId) { + synchronized (mSnoozedForAlarm) { + mSnoozedForAlarm.add(conditionId); saveSnoozedLocked(); } } private void removeSnoozed(Uri conditionId) { - synchronized (mSnoozed) { - mSnoozed.remove(conditionId); + synchronized (mSnoozedForAlarm) { + mSnoozedForAlarm.remove(conditionId); saveSnoozedLocked(); } } - public void saveSnoozedLocked() { - final String setting = TextUtils.join(SEPARATOR, mSnoozed); + private void saveSnoozedLocked() { + final String setting = TextUtils.join(SEPARATOR, mSnoozedForAlarm); final int currentUser = ActivityManager.getCurrentUser(); Settings.Secure.putStringForUser(mContext.getContentResolver(), SCP_SETTING, @@ -294,8 +308,8 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { currentUser); } - public void readSnoozed() { - synchronized (mSnoozed) { + private void readSnoozed() { + synchronized (mSnoozedForAlarm) { long identity = Binder.clearCallingIdentity(); try { final String setting = Settings.Secure.getStringForUser( @@ -312,7 +326,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { if (TextUtils.isEmpty(token)) { continue; } - mSnoozed.add(Uri.parse(token)); + mSnoozedForAlarm.add(Uri.parse(token)); } } } finally { diff --git a/services/tests/notification/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/notification/src/com/android/server/notification/ScheduleCalendarTest.java new file mode 100644 index 000000000000..cbda12da1a6f --- /dev/null +++ b/services/tests/notification/src/com/android/server/notification/ScheduleCalendarTest.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.service.notification.ZenModeConfig; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Slog; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ScheduleCalendarTest extends NotificationTestCase { + + private ScheduleCalendar mScheduleCalendar; + private ZenModeConfig.ScheduleInfo mScheduleInfo; + + @Before + public void setUp() throws Exception { + mScheduleCalendar = new ScheduleCalendar(); + mScheduleInfo = new ZenModeConfig.ScheduleInfo(); + mScheduleInfo.days = new int[] {1, 2, 3, 4, 5}; + mScheduleCalendar.setSchedule(mScheduleInfo); + } + + @Test + public void testNullScheduleInfo() throws Exception { + mScheduleCalendar.setSchedule(null); + + mScheduleCalendar.maybeSetNextAlarm(1000, 1999); + assertEquals(0, mScheduleCalendar.getNextChangeTime(1000)); + assertFalse(mScheduleCalendar.isInSchedule(100)); + assertFalse(mScheduleCalendar.shouldExitForAlarm(100)); + } + + @Test + public void testGetNextChangeTime_startToday() throws Exception { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 1); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + mScheduleInfo.days = new int[] {getTodayDay()}; + mScheduleInfo.startHour = cal.get(Calendar.HOUR_OF_DAY) + 1; + mScheduleInfo.endHour = cal.get(Calendar.HOUR_OF_DAY) + 3; + mScheduleInfo.startMinute = 15; + mScheduleInfo.endMinute = 15; + mScheduleInfo.exitAtAlarm = false; + mScheduleCalendar.setSchedule(mScheduleInfo); + + Calendar expected = new GregorianCalendar(); + expected.setTimeInMillis(cal.getTimeInMillis()); + expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour); + + long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis()); + GregorianCalendar actual = new GregorianCalendar(); + actual.setTimeInMillis(actualMs); + assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(), + actualMs); + } + + @Test + public void testGetNextChangeTime_endToday() throws Exception { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 2); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + mScheduleInfo.days = new int[] {getTodayDay()}; + mScheduleInfo.startHour = cal.get(Calendar.HOUR_OF_DAY) - 1; + mScheduleInfo.endHour = cal.get(Calendar.HOUR_OF_DAY) + 3; + mScheduleInfo.startMinute = 15; + mScheduleInfo.endMinute = 15; + mScheduleInfo.exitAtAlarm = false; + + Calendar expected = new GregorianCalendar(); + expected.setTimeInMillis(cal.getTimeInMillis()); + expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.endHour); + expected.set(Calendar.MINUTE, mScheduleInfo.endMinute); + + long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis()); + GregorianCalendar actual = new GregorianCalendar(); + actual.setTimeInMillis(actualMs); + assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(), + actualMs); + } + + @Test + public void testGetNextChangeTime_startTomorrow() throws Exception { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 23); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1}; + mScheduleInfo.startHour = 1; + mScheduleInfo.endHour = 3; + mScheduleInfo.startMinute = 15; + mScheduleInfo.endMinute = 15; + mScheduleInfo.exitAtAlarm = false; + + Calendar expected = new GregorianCalendar(); + expected.setTimeInMillis(cal.getTimeInMillis()); + expected.add(Calendar.DATE, 1); + expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour); + expected.set(Calendar.MINUTE, mScheduleInfo.startMinute); + + long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis()); + GregorianCalendar actual = new GregorianCalendar(); + actual.setTimeInMillis(actualMs); + assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(), + actualMs); + } + + @Test + public void testGetNextChangeTime_endTomorrow() throws Exception { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 23); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1}; + mScheduleInfo.startHour = 22; + mScheduleInfo.endHour = 3; + mScheduleInfo.startMinute = 15; + mScheduleInfo.endMinute = 15; + mScheduleInfo.exitAtAlarm = false; + + Calendar expected = new GregorianCalendar(); + expected.setTimeInMillis(cal.getTimeInMillis()); + expected.add(Calendar.DATE, 1); + expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.endHour); + expected.set(Calendar.MINUTE, mScheduleInfo.endMinute); + + long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis()); + GregorianCalendar actual = new GregorianCalendar(); + actual.setTimeInMillis(actualMs); + assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(), + actualMs); + } + + @Test + public void testShouldExitForAlarm_settingOff() { + mScheduleInfo.exitAtAlarm = false; + mScheduleInfo.nextAlarm = 1000; + + assertFalse(mScheduleCalendar.shouldExitForAlarm(1000)); + } + + @Test + public void testShouldExitForAlarm_beforeAlarm() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 1000; + + assertFalse(mScheduleCalendar.shouldExitForAlarm(999)); + } + + @Test + public void testShouldExitForAlarm_noAlarm() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 0; + + assertFalse(mScheduleCalendar.shouldExitForAlarm(999)); + } + + @Test + public void testShouldExitForAlarm() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 1000; + + assertTrue(mScheduleCalendar.shouldExitForAlarm(1000)); + } + + @Test + public void testMaybeSetNextAlarm_settingOff() { + mScheduleInfo.exitAtAlarm = false; + mScheduleInfo.nextAlarm = 0; + + mScheduleCalendar.maybeSetNextAlarm(1000, 2000); + + assertEquals(0, mScheduleInfo.nextAlarm); + } + + @Test + public void testMaybeSetNextAlarm_settingOn() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 0; + + mScheduleCalendar.maybeSetNextAlarm(1000, 2000); + + assertEquals(2000, mScheduleInfo.nextAlarm); + } + + @Test + public void testMaybeSetNextAlarm_alarmCanceled() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 10000; + + mScheduleCalendar.maybeSetNextAlarm(1000, 0); + + assertEquals(0, mScheduleInfo.nextAlarm); + } + + @Test + public void testMaybeSetNextAlarm_earlierAlarm() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 2000; + + mScheduleCalendar.maybeSetNextAlarm(1000, 1500); + + assertEquals(1500, mScheduleInfo.nextAlarm); + } + + @Test + public void testMaybeSetNextAlarm_laterAlarm() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 2000; + + mScheduleCalendar.maybeSetNextAlarm(1000, 3000); + + assertEquals(2000, mScheduleInfo.nextAlarm); + } + + @Test + public void testMaybeSetNextAlarm_expiredAlarm() { + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 998; + + mScheduleCalendar.maybeSetNextAlarm(1000, 999); + + assertEquals(0, mScheduleInfo.nextAlarm); + } + + @Test + public void testIsInSchedule_inScheduleOvernight() { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 23); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + mScheduleInfo.days = new int[] {getTodayDay()}; + mScheduleInfo.startHour = 22; + mScheduleInfo.endHour = 3; + mScheduleInfo.startMinute = 15; + mScheduleInfo.endMinute = 15; + + assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); + } + + @Test + public void testIsInSchedule_inScheduleSingleDay() { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 14); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + mScheduleInfo.days = new int[] {getTodayDay()}; + mScheduleInfo.startHour = 12; + mScheduleInfo.endHour = 3; + mScheduleInfo.startMinute = 16; + mScheduleInfo.endMinute = 15; + + assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); + } + + @Test + public void testIsInSchedule_notToday() { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 14); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY); + mScheduleInfo.days = new int[] {Calendar.FRIDAY, Calendar.SUNDAY}; + mScheduleInfo.startHour = 12; + mScheduleInfo.startMinute = 16; + mScheduleInfo.endHour = 15; + mScheduleInfo.endMinute = 15; + + assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); + } + + @Test + public void testIsInSchedule_startingSoon() { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 14); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 59); + cal.set(Calendar.MILLISECOND, 0); + mScheduleInfo.days = new int[] {getTodayDay()}; + mScheduleInfo.startHour = 14; + mScheduleInfo.endHour = 3; + mScheduleInfo.startMinute = 16; + mScheduleInfo.endMinute = 15; + + assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); + } + + private int getTodayDay() { + return new GregorianCalendar().get(Calendar.DAY_OF_WEEK); + } +} diff --git a/services/tests/notification/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/notification/src/com/android/server/notification/ScheduleConditionProviderTest.java new file mode 100644 index 000000000000..ddf46a052e40 --- /dev/null +++ b/services/tests/notification/src/com/android/server/notification/ScheduleConditionProviderTest.java @@ -0,0 +1,337 @@ +package com.android.server.notification; + +import static org.mockito.Mockito.spy; + +import android.content.Intent; +import android.net.Uri; +import android.os.Looper; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.support.test.InstrumentationRegistry; +import android.test.ServiceTestCase; +import android.testing.TestableContext; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +public class ScheduleConditionProviderTest extends ServiceTestCase<ScheduleConditionProvider> { + + ScheduleConditionProvider mService; + + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getContext(), null); + + public ScheduleConditionProviderTest() { + super(ScheduleConditionProvider.class); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Looper.prepare(); + + Intent startIntent = + new Intent("com.android.server.notification.ScheduleConditionProvider"); + startIntent.setPackage("android"); + bindService(startIntent); + mService = spy(getService()); + } + + @Test + public void testIsValidConditionId_incomplete() throws Exception { + Uri badConditionId = Uri.EMPTY; + assertFalse(mService.isValidConditionId(badConditionId)); + assertEquals(Condition.STATE_ERROR, + mService.evaluateSubscriptionLocked(badConditionId, null, 0, 1000).state); + } + + @Test + public void testIsValidConditionId() throws Exception { + ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); + info.days = new int[] {1, 2, 4}; + info.startHour = 8; + info.startMinute = 56; + info.nextAlarm = 1000; + info.exitAtAlarm = true; + info.endHour = 12; + info.endMinute = 9; + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + assertTrue(mService.isValidConditionId(conditionId)); + } + + @Test + public void testEvaluateSubscription_noAlarmExit_InSchedule() { + Calendar now = getNow(); + + // Schedule - 1 hour long; starts now + ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); + info.days = new int[] {Calendar.FRIDAY}; + info.startHour = now.get(Calendar.HOUR_OF_DAY); + info.startMinute = now.get(Calendar.MINUTE); + info.nextAlarm = 0; + info.exitAtAlarm = false; + info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1; + info.endMinute = info.startMinute; + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + assertTrue(cal.isInSchedule(now.getTimeInMillis())); + + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000); + + assertEquals(Condition.STATE_TRUE, condition.state); + } + + @Test + public void testEvaluateSubscription_noAlarmExit_InScheduleSnoozed() { + Calendar now = getNow(); + + // Schedule - 1 hour long; starts now + ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); + info.days = new int[] {Calendar.FRIDAY}; + info.startHour = now.get(Calendar.HOUR_OF_DAY); + info.startMinute = now.get(Calendar.MINUTE); + info.nextAlarm = 0; + info.exitAtAlarm = false; + info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1; + info.endMinute = info.startMinute; + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + assertTrue(cal.isInSchedule(now.getTimeInMillis())); + + mService.addSnoozed(conditionId); + + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000); + + assertEquals(Condition.STATE_FALSE, condition.state); + } + + @Test + public void testEvaluateSubscription_noAlarmExit_beforeSchedule() { + Calendar now = new GregorianCalendar(); + now.set(Calendar.HOUR_OF_DAY, 14); + now.set(Calendar.MINUTE, 15); + now.set(Calendar.SECOND, 59); + now.set(Calendar.MILLISECOND, 0); + now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); + + // Schedule - 1 hour long; starts in 1 second + ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); + info.days = new int[] {Calendar.FRIDAY}; + info.startHour = now.get(Calendar.HOUR_OF_DAY); + info.startMinute = now.get(Calendar.MINUTE) + 1; + info.nextAlarm = 0; + info.exitAtAlarm = false; + info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1; + info.endMinute = info.startMinute; + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000); + + assertEquals(Condition.STATE_FALSE, condition.state); + } + + @Test + public void testEvaluateSubscription_noAlarmExit_endSchedule() { + Calendar now = getNow(); + + // Schedule - 1 hour long; ends now + ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); + info.days = new int[] {Calendar.FRIDAY}; + info.startHour = now.get(Calendar.HOUR_OF_DAY) - 1; + info.startMinute = now.get(Calendar.MINUTE); + info.nextAlarm = 0; + info.exitAtAlarm = false; + info.endHour = now.get(Calendar.HOUR_OF_DAY); + info.endMinute = now.get(Calendar.MINUTE); + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000); + + assertEquals(Condition.STATE_FALSE, condition.state); + } + + @Test + public void testEvaluateSubscription_alarmSetBeforeInSchedule() { + Calendar now = getNow(); + + // Schedule - 1 hour long; starts now, ends with alarm + ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now); + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + + // an hour before start, update with an alarm that will fire during the schedule + mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() - 1000, now.getTimeInMillis() + 1000); + + // at start, should be in dnd + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000); + assertEquals(Condition.STATE_TRUE, condition.state); + + // at alarm fire time, should exit dnd + assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000)); + assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(), + cal.shouldExitForAlarm(now.getTimeInMillis() + 1000)); + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 1000, 0); + assertEquals(Condition.STATE_FALSE, condition.state); + } + + @Test + public void testEvaluateSubscription_alarmSetInSchedule() { + Calendar now = getNow(); + + // Schedule - 1 hour long; starts now, ends with alarm + ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now); + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + + // at start, should be in dnd + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), 0); + assertEquals(Condition.STATE_TRUE, condition.state); + + // in schedule, update with alarm time, should be in dnd + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000); + assertEquals(Condition.STATE_TRUE, condition.state); + + // at alarm fire time, should exit dnd + assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000)); + assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(), + cal.shouldExitForAlarm(now.getTimeInMillis() + 1000)); + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 1000, 0); + assertEquals(Condition.STATE_FALSE, condition.state); + } + + @Test + public void testEvaluateSubscription_earlierAlarmSet() { + Calendar now = getNow(); + + // Schedule - 1 hour long; starts now, ends with alarm + ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now); + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + + // at start, should be in dnd, alarm in 2000 ms + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 2000); + assertEquals(Condition.STATE_TRUE, condition.state); + + // in schedule, update with earlier alarm time, should be in dnd + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000); + assertEquals(Condition.STATE_TRUE, condition.state); + + // at earliest alarm fire time, should exit dnd + assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000)); + assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(), + cal.shouldExitForAlarm(now.getTimeInMillis() + 1000)); + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 1000, 0); + assertEquals(Condition.STATE_FALSE, condition.state); + } + + @Test + public void testEvaluateSubscription_laterAlarmSet() { + Calendar now = getNow(); + + // Schedule - 1 hour long; starts now, ends with alarm + ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now); + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + + // at start, should be in dnd, alarm in 500 ms + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500); + assertEquals(Condition.STATE_TRUE, condition.state); + + // in schedule, update with later alarm time, should be in dnd + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 250, now.getTimeInMillis() + 1000); + assertEquals(Condition.STATE_TRUE, condition.state); + + // at earliest alarm fire time, should exit dnd + assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500)); + assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(), + cal.shouldExitForAlarm(now.getTimeInMillis() + 500)); + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 500, 0); + assertEquals(Condition.STATE_FALSE, condition.state); + } + + @Test + public void testEvaluateSubscription_alarmCanceled() { + Calendar now = getNow(); + + // Schedule - 1 hour long; starts now, ends with alarm + ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now); + Uri conditionId = ZenModeConfig.toScheduleConditionId(info); + ScheduleCalendar cal = new ScheduleCalendar(); + cal.setSchedule(info); + + // at start, should be in dnd, alarm in 500 ms + Condition condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500); + assertEquals(Condition.STATE_TRUE, condition.state); + + // in schedule, cancel alarm + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 250, 0); + assertEquals(Condition.STATE_TRUE, condition.state); + + // at previous alarm time, should not exit DND + assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500)); + assertFalse(cal.shouldExitForAlarm(now.getTimeInMillis() + 500)); + condition = mService.evaluateSubscriptionLocked( + conditionId, cal, now.getTimeInMillis() + 500, 0); + assertEquals(Condition.STATE_TRUE, condition.state); + + // end of schedule, exit DND + now.add(Calendar.HOUR_OF_DAY, 1); + condition = mService.evaluateSubscriptionLocked(conditionId, cal, now.getTimeInMillis(), 0); + assertEquals(Condition.STATE_FALSE, condition.state); + } + + private Calendar getNow() { + Calendar now = new GregorianCalendar(); + now.set(Calendar.HOUR_OF_DAY, 14); + now.set(Calendar.MINUTE, 16); + now.set(Calendar.SECOND, 0); + now.set(Calendar.MILLISECOND, 0); + now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); + return now; + } + + private ZenModeConfig.ScheduleInfo getScheduleEndsInHour(Calendar now) { + ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); + info.days = new int[] {Calendar.FRIDAY}; + info.startHour = now.get(Calendar.HOUR_OF_DAY); + info.startMinute = now.get(Calendar.MINUTE); + info.exitAtAlarm = true; + info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1; + info.endMinute = now.get(Calendar.MINUTE); + return info; + } +} |