Loading services/core/java/com/android/server/notification/ScheduleConditionProvider.java +38 −11 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import com.android.server.notification.NotificationManagerService.DumpFilter; import com.android.server.pm.PackageManagerService; import java.io.PrintWriter; import java.time.Clock; import java.util.ArrayList; import java.util.Calendar; import java.util.List; Loading @@ -62,6 +63,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private static final String SCP_SETTING = "snoozed_schedule_condition_provider"; private final Context mContext = this; private final Clock mClock; private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>(); @GuardedBy("mSnoozedForAlarm") private final ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>(); Loading @@ -72,7 +74,13 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private long mNextAlarmTime; public ScheduleConditionProvider() { this(Clock.systemUTC()); } @VisibleForTesting ScheduleConditionProvider(Clock clock) { if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); mClock = clock; } @Override Loading @@ -86,7 +94,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { pw.print(" mConnected="); pw.println(mConnected); pw.print(" mRegistered="); pw.println(mRegistered); pw.println(" mSubscriptions="); final long now = System.currentTimeMillis(); final long now = mClock.millis(); synchronized (mSubscriptions) { for (Uri conditionId : mSubscriptions.keySet()) { pw.print(" "); Loading Loading @@ -117,7 +125,9 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { @Override public void onUserSwitched(UserHandle user) { // Nothing to do because time-based schedules are not tied to any user data. // Nothing to do here because evaluateSubscriptions() is called for the new configuration // when users switch, and that will reevaluate the next alarm, which is the only piece that // is user-dependent. } @Override Loading Loading @@ -151,12 +161,9 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } private void evaluateSubscriptions() { if (mAlarmManager == null) { mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); } final long now = System.currentTimeMillis(); final long now = mClock.millis(); mNextAlarmTime = 0; long nextUserAlarmTime = getNextAlarm(); long nextUserAlarmTime = getNextAlarmClockAlarm(); List<Condition> conditionsToNotify = new ArrayList<>(); synchronized (mSubscriptions) { setRegistered(!mSubscriptions.isEmpty()); Loading Loading @@ -232,7 +239,10 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } public long getNextAlarm() { private long getNextAlarmClockAlarm() { if (mAlarmManager == null) { mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); } final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock( ActivityManager.getCurrentUser()); return info != null ? info.getTriggerTime() : 0; Loading @@ -252,8 +262,13 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(ACTION_EVALUATE); filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); if (android.app.Flags.modesHsum()) { registerReceiverForAllUsers(mReceiver, filter, /* broadcastPermission= */ null, /* scheduler= */ null); } else { registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); } } else { unregisterReceiver(mReceiver); } Loading Loading @@ -327,10 +342,18 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } } private BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); if (android.app.Flags.modesHsum()) { if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(intent.getAction()) && getSendingUserId() != ActivityManager.getCurrentUser()) { // A different user changed their next alarm. return; } } if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { synchronized (mSubscriptions) { for (Uri conditionId : mSubscriptions.keySet()) { Loading @@ -345,4 +368,8 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } }; @VisibleForTesting // otherwise = NONE public ArrayMap<Uri, ScheduleCalendar> getSubscriptions() { return mSubscriptions; } } services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java +153 −4 Original line number Diff line number Diff line package com.android.server.notification; import static android.app.AlarmManager.RTC_WAKEUP; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static java.time.temporal.ChronoUnit.HOURS; import static java.time.temporal.ChronoUnit.MINUTES; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.Application; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Bundle; import android.os.SimpleClock; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.Condition; import android.service.notification.ScheduleCalendar; import android.service.notification.ZenModeConfig; Loading @@ -24,11 +44,20 @@ import androidx.test.filters.SmallTest; import com.android.server.UiServiceTestCase; import com.android.server.pm.PackageManagerService; import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.GregorianCalendar; Loading @@ -36,17 +65,22 @@ import java.util.GregorianCalendar; @SmallTest @RunWithLooper public class ScheduleConditionProviderTest extends UiServiceTestCase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); ScheduleConditionProvider mService; private ScheduleConditionProvider mService; private TestClock mClock = new TestClock(); @Mock private AlarmManager mAlarmManager; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(AlarmManager.class, mAlarmManager); Intent startIntent = new Intent("com.android.server.notification.ScheduleConditionProvider"); startIntent.setPackage("android"); ScheduleConditionProvider service = new ScheduleConditionProvider(); ScheduleConditionProvider service = new ScheduleConditionProvider(mClock); service.attach( getContext(), null, // ActivityThread not actually used in Service Loading @@ -57,7 +91,7 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { ); service.onCreate(); service.onBind(startIntent); mService = spy(service); mService = service; } @Test Loading Loading @@ -343,6 +377,87 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage()); } @Test @EnableFlags(android.app.Flags.FLAG_MODES_HSUM) public void onSubscribe_registersReceiverForAllUsers() { Calendar now = getNow(); Uri condition = ZenModeConfig.toScheduleConditionId(getScheduleEndsInHour(now)); mService.onSubscribe(condition); ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class); verify(mContext).registerReceiverForAllUsers(any(), filterCaptor.capture(), any(), any()); IntentFilter filter = filterCaptor.getValue(); assertThat(filter.actionsIterator()).isNotNull(); assertThat(ImmutableList.copyOf(filter.actionsIterator())) .contains(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); } @Test @EnableFlags(android.app.Flags.FLAG_MODES_HSUM) public void onAlarmClockChanged_storesNextAlarm() { Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z"); Instant scheduleEnd = scheduleStart.plus(1, HOURS); Instant now = scheduleStart.plus(15, MINUTES); mClock.setNowMillis(now.toEpochMilli()); Uri condition = ZenModeConfig.toScheduleConditionId( getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault()))); mService.onSubscribe(condition); // Now prepare to send an "alarm set for 16:30" broadcast. Instant alarm = scheduleStart.plus(30, MINUTES); ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any()); BroadcastReceiver receiver = receiverCaptor.getValue(); receiver.setPendingResult(pendingResultForUserBroadcast(ActivityManager.getCurrentUser())); when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn( new AlarmManager.AlarmClockInfo(alarm.toEpochMilli(), null)); Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); receiver.onReceive(mContext, intent); // The time for the alarm was stored in the ScheduleCalendar, meaning the rule will end when // the next evaluation after that point happens. ScheduleCalendar scheduleCalendar = mService.getSubscriptions().values().stream().findFirst().get(); assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() - 1)).isFalse(); assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() + 1)).isTrue(); // But the next wakeup is unchanged, at the time of the schedule end (17:00). verify(mAlarmManager, times(2)).setExact(eq(RTC_WAKEUP), eq(scheduleEnd.toEpochMilli()), any()); } @Test @EnableFlags(android.app.Flags.FLAG_MODES_HSUM) public void onAlarmClockChanged_forAnotherUser_isIgnored() { Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z"); Instant now = scheduleStart.plus(15, MINUTES); mClock.setNowMillis(now.toEpochMilli()); Uri condition = ZenModeConfig.toScheduleConditionId( getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault()))); mService.onSubscribe(condition); // Now prepare to send an "alarm set for a different user" broadcast. ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any()); BroadcastReceiver receiver = receiverCaptor.getValue(); reset(mAlarmManager); int anotherUser = ActivityManager.getCurrentUser() + 1; receiver.setPendingResult(pendingResultForUserBroadcast(anotherUser)); Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); receiver.onReceive(mContext, intent); // The alarm data was not read. verify(mAlarmManager, never()).getNextAlarmClock(anyInt()); } private Calendar getNow() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 14); Loading @@ -363,4 +478,38 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { info.endMinute = now.get(Calendar.MINUTE); return info; } private static ZenModeConfig.ScheduleInfo getOneHourSchedule(ZonedDateTime start) { ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); // Note: DayOfWeek.MONDAY doesn't match Calendar.MONDAY info.days = new int[] { (start.getDayOfWeek().getValue() % 7) + 1 }; info.startHour = start.getHour(); info.startMinute = start.getMinute(); info.endHour = start.plusHours(1).getHour(); info.endMinute = start.plusHours(1).getMinute(); info.exitAtAlarm = true; return info; } private static BroadcastReceiver.PendingResult pendingResultForUserBroadcast(int userId) { return new BroadcastReceiver.PendingResult(0, "", new Bundle(), 0, false, false, null, userId, 0); } private static class TestClock extends SimpleClock { private long mNowMillis = 441644400000L; private TestClock() { super(ZoneOffset.UTC); } @Override public long millis() { return mNowMillis; } private void setNowMillis(long millis) { mNowMillis = millis; } } } Loading
services/core/java/com/android/server/notification/ScheduleConditionProvider.java +38 −11 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import com.android.server.notification.NotificationManagerService.DumpFilter; import com.android.server.pm.PackageManagerService; import java.io.PrintWriter; import java.time.Clock; import java.util.ArrayList; import java.util.Calendar; import java.util.List; Loading @@ -62,6 +63,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private static final String SCP_SETTING = "snoozed_schedule_condition_provider"; private final Context mContext = this; private final Clock mClock; private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>(); @GuardedBy("mSnoozedForAlarm") private final ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>(); Loading @@ -72,7 +74,13 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private long mNextAlarmTime; public ScheduleConditionProvider() { this(Clock.systemUTC()); } @VisibleForTesting ScheduleConditionProvider(Clock clock) { if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()"); mClock = clock; } @Override Loading @@ -86,7 +94,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { pw.print(" mConnected="); pw.println(mConnected); pw.print(" mRegistered="); pw.println(mRegistered); pw.println(" mSubscriptions="); final long now = System.currentTimeMillis(); final long now = mClock.millis(); synchronized (mSubscriptions) { for (Uri conditionId : mSubscriptions.keySet()) { pw.print(" "); Loading Loading @@ -117,7 +125,9 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { @Override public void onUserSwitched(UserHandle user) { // Nothing to do because time-based schedules are not tied to any user data. // Nothing to do here because evaluateSubscriptions() is called for the new configuration // when users switch, and that will reevaluate the next alarm, which is the only piece that // is user-dependent. } @Override Loading Loading @@ -151,12 +161,9 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } private void evaluateSubscriptions() { if (mAlarmManager == null) { mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); } final long now = System.currentTimeMillis(); final long now = mClock.millis(); mNextAlarmTime = 0; long nextUserAlarmTime = getNextAlarm(); long nextUserAlarmTime = getNextAlarmClockAlarm(); List<Condition> conditionsToNotify = new ArrayList<>(); synchronized (mSubscriptions) { setRegistered(!mSubscriptions.isEmpty()); Loading Loading @@ -232,7 +239,10 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } public long getNextAlarm() { private long getNextAlarmClockAlarm() { if (mAlarmManager == null) { mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); } final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock( ActivityManager.getCurrentUser()); return info != null ? info.getTriggerTime() : 0; Loading @@ -252,8 +262,13 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(ACTION_EVALUATE); filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); if (android.app.Flags.modesHsum()) { registerReceiverForAllUsers(mReceiver, filter, /* broadcastPermission= */ null, /* scheduler= */ null); } else { registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); } } else { unregisterReceiver(mReceiver); } Loading Loading @@ -327,10 +342,18 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } } private BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); if (android.app.Flags.modesHsum()) { if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(intent.getAction()) && getSendingUserId() != ActivityManager.getCurrentUser()) { // A different user changed their next alarm. return; } } if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { synchronized (mSubscriptions) { for (Uri conditionId : mSubscriptions.keySet()) { Loading @@ -345,4 +368,8 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } }; @VisibleForTesting // otherwise = NONE public ArrayMap<Uri, ScheduleCalendar> getSubscriptions() { return mSubscriptions; } }
services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java +153 −4 Original line number Diff line number Diff line package com.android.server.notification; import static android.app.AlarmManager.RTC_WAKEUP; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static java.time.temporal.ChronoUnit.HOURS; import static java.time.temporal.ChronoUnit.MINUTES; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.Application; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Bundle; import android.os.SimpleClock; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.Condition; import android.service.notification.ScheduleCalendar; import android.service.notification.ZenModeConfig; Loading @@ -24,11 +44,20 @@ import androidx.test.filters.SmallTest; import com.android.server.UiServiceTestCase; import com.android.server.pm.PackageManagerService; import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.GregorianCalendar; Loading @@ -36,17 +65,22 @@ import java.util.GregorianCalendar; @SmallTest @RunWithLooper public class ScheduleConditionProviderTest extends UiServiceTestCase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); ScheduleConditionProvider mService; private ScheduleConditionProvider mService; private TestClock mClock = new TestClock(); @Mock private AlarmManager mAlarmManager; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(AlarmManager.class, mAlarmManager); Intent startIntent = new Intent("com.android.server.notification.ScheduleConditionProvider"); startIntent.setPackage("android"); ScheduleConditionProvider service = new ScheduleConditionProvider(); ScheduleConditionProvider service = new ScheduleConditionProvider(mClock); service.attach( getContext(), null, // ActivityThread not actually used in Service Loading @@ -57,7 +91,7 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { ); service.onCreate(); service.onBind(startIntent); mService = spy(service); mService = service; } @Test Loading Loading @@ -343,6 +377,87 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage()); } @Test @EnableFlags(android.app.Flags.FLAG_MODES_HSUM) public void onSubscribe_registersReceiverForAllUsers() { Calendar now = getNow(); Uri condition = ZenModeConfig.toScheduleConditionId(getScheduleEndsInHour(now)); mService.onSubscribe(condition); ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class); verify(mContext).registerReceiverForAllUsers(any(), filterCaptor.capture(), any(), any()); IntentFilter filter = filterCaptor.getValue(); assertThat(filter.actionsIterator()).isNotNull(); assertThat(ImmutableList.copyOf(filter.actionsIterator())) .contains(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); } @Test @EnableFlags(android.app.Flags.FLAG_MODES_HSUM) public void onAlarmClockChanged_storesNextAlarm() { Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z"); Instant scheduleEnd = scheduleStart.plus(1, HOURS); Instant now = scheduleStart.plus(15, MINUTES); mClock.setNowMillis(now.toEpochMilli()); Uri condition = ZenModeConfig.toScheduleConditionId( getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault()))); mService.onSubscribe(condition); // Now prepare to send an "alarm set for 16:30" broadcast. Instant alarm = scheduleStart.plus(30, MINUTES); ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any()); BroadcastReceiver receiver = receiverCaptor.getValue(); receiver.setPendingResult(pendingResultForUserBroadcast(ActivityManager.getCurrentUser())); when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn( new AlarmManager.AlarmClockInfo(alarm.toEpochMilli(), null)); Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); receiver.onReceive(mContext, intent); // The time for the alarm was stored in the ScheduleCalendar, meaning the rule will end when // the next evaluation after that point happens. ScheduleCalendar scheduleCalendar = mService.getSubscriptions().values().stream().findFirst().get(); assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() - 1)).isFalse(); assertThat(scheduleCalendar.shouldExitForAlarm(alarm.toEpochMilli() + 1)).isTrue(); // But the next wakeup is unchanged, at the time of the schedule end (17:00). verify(mAlarmManager, times(2)).setExact(eq(RTC_WAKEUP), eq(scheduleEnd.toEpochMilli()), any()); } @Test @EnableFlags(android.app.Flags.FLAG_MODES_HSUM) public void onAlarmClockChanged_forAnotherUser_isIgnored() { Instant scheduleStart = Instant.parse("2024-10-22T16:00:00Z"); Instant now = scheduleStart.plus(15, MINUTES); mClock.setNowMillis(now.toEpochMilli()); Uri condition = ZenModeConfig.toScheduleConditionId( getOneHourSchedule(scheduleStart.atZone(ZoneId.systemDefault()))); mService.onSubscribe(condition); // Now prepare to send an "alarm set for a different user" broadcast. ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); verify(mContext).registerReceiverForAllUsers(receiverCaptor.capture(), any(), any(), any()); BroadcastReceiver receiver = receiverCaptor.getValue(); reset(mAlarmManager); int anotherUser = ActivityManager.getCurrentUser() + 1; receiver.setPendingResult(pendingResultForUserBroadcast(anotherUser)); Intent intent = new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); receiver.onReceive(mContext, intent); // The alarm data was not read. verify(mAlarmManager, never()).getNextAlarmClock(anyInt()); } private Calendar getNow() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 14); Loading @@ -363,4 +478,38 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { info.endMinute = now.get(Calendar.MINUTE); return info; } private static ZenModeConfig.ScheduleInfo getOneHourSchedule(ZonedDateTime start) { ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo(); // Note: DayOfWeek.MONDAY doesn't match Calendar.MONDAY info.days = new int[] { (start.getDayOfWeek().getValue() % 7) + 1 }; info.startHour = start.getHour(); info.startMinute = start.getMinute(); info.endHour = start.plusHours(1).getHour(); info.endMinute = start.plusHours(1).getMinute(); info.exitAtAlarm = true; return info; } private static BroadcastReceiver.PendingResult pendingResultForUserBroadcast(int userId) { return new BroadcastReceiver.PendingResult(0, "", new Bundle(), 0, false, false, null, userId, 0); } private static class TestClock extends SimpleClock { private long mNowMillis = 441644400000L; private TestClock() { super(ZoneOffset.UTC); } @Override public long millis() { return mNowMillis; } private void setNowMillis(long millis) { mNowMillis = millis; } } }