Loading services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java +141 −116 Original line number Diff line number Diff line Loading @@ -65,51 +65,47 @@ import java.time.Duration; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; /** * An implementation of {@link TimeZoneChangeListener} that fires notifications. */ /** An implementation of {@link TimeZoneChangeListener} that fires notifications. */ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { @IntDef({STATUS_UNKNOWN, STATUS_UNTRACKED, STATUS_REJECTED, STATUS_ACCEPTED, STATUS_SUPERSEDED}) @IntDef({STATUS_UNKNOWN, STATUS_UNTRACKED, STATUS_REJECTED, STATUS_ACCEPTED, STATUS_SUPERSEDED}) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TimeZoneChangeStatus {} /** Used to indicate the status could not be inferred. */ @TimeZoneChangeStatus static final int STATUS_UNKNOWN = 0; @TimeZoneChangeStatus static final int STATUS_UNKNOWN = 0; /** Used to indicate the change is not one that needs to be tracked. */ @TimeZoneChangeStatus static final int STATUS_UNTRACKED = 1; @TimeZoneChangeStatus static final int STATUS_REJECTED = 2; @TimeZoneChangeStatus static final int STATUS_ACCEPTED = 3; /** Used to indicate a change was superseded before its status could be determined. */ @TimeZoneChangeStatus static final int STATUS_SUPERSEDED = 4; @TimeZoneChangeStatus static final int STATUS_UNTRACKED = 1; @TimeZoneChangeStatus static final int STATUS_REJECTED = 2; @TimeZoneChangeStatus static final int STATUS_ACCEPTED = 3; @IntDef({SIGNAL_TYPE_UNKNOWN, SIGNAL_TYPE_NONE, SIGNAL_TYPE_NOTIFICATION, SIGNAL_TYPE_HEURISTIC}) /** Used to indicate a change was superseded before its status could be determined. */ @TimeZoneChangeStatus static final int STATUS_SUPERSEDED = 4; @IntDef({ SIGNAL_TYPE_UNKNOWN, SIGNAL_TYPE_NONE, SIGNAL_TYPE_NOTIFICATION, SIGNAL_TYPE_HEURISTIC }) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface SignalType {} /** Used when the signal type cannot be inferred. */ @SignalType static final int SIGNAL_TYPE_UNKNOWN = 0; @SignalType static final int SIGNAL_TYPE_UNKNOWN = 0; /** Used when the status is not one that needs a signal type. */ @SignalType static final int SIGNAL_TYPE_NONE = 1; @SignalType static final int SIGNAL_TYPE_NOTIFICATION = 2; @SignalType static final int SIGNAL_TYPE_HEURISTIC = 3; @SignalType static final int SIGNAL_TYPE_NONE = 1; @SignalType static final int SIGNAL_TYPE_NOTIFICATION = 2; @SignalType static final int SIGNAL_TYPE_HEURISTIC = 3; private static final int MAX_EVENTS_TO_TRACK = 10; @VisibleForTesting @DurationMillisLong @VisibleForTesting @DurationMillisLong static final long AUTO_REVERT_THRESHOLD = Duration.ofMinutes(15).toMillis(); private static final String TAG = "TimeZoneChangeTracker"; Loading Loading @@ -137,15 +133,18 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { private final ReferenceWithHistory<TimeZoneChangeRecord> mTimeZoneChangeRecord = new ReferenceWithHistory<>(MAX_EVENTS_TO_TRACK); private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_NOTIFICATION_DELETED: int notifiedUserId = intent.getIntExtra( NOTIFICATION_INTENT_EXTRA_USER_ID, UserHandle.USER_NULL); int changeEventId = intent.getIntExtra( NOTIFICATION_INTENT_EXTRA_CHANGE_ID, 0); int notifiedUserId = intent.getIntExtra( NOTIFICATION_INTENT_EXTRA_USER_ID, UserHandle.USER_NULL); int changeEventId = intent.getIntExtra(NOTIFICATION_INTENT_EXTRA_CHANGE_ID, 0); notificationSwipedAway(notifiedUserId, changeEventId); break; default: Loading @@ -154,12 +153,13 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } }; @NonNull private final Environment mEnvironment; @NonNull private final Environment mEnvironment; private final Object mConfigurationLock = new Object(); @GuardedBy("mConfigurationLock") private ConfigurationInternal mConfigurationInternal; @GuardedBy("mConfigurationLock") private boolean mIsRegistered; Loading @@ -173,11 +173,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { /** Create and initialise a new {@code TimeZoneChangeTrackerImpl} */ @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public static NotifyingTimeZoneChangeListener create(Handler handler, Context context, public static NotifyingTimeZoneChangeListener create( Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor, @NonNull Environment environment) { NotifyingTimeZoneChangeListener changeTracker = new NotifyingTimeZoneChangeListener(handler, new NotifyingTimeZoneChangeListener( handler, context, serviceConfigAccessor, context.getSystemService(NotificationManager.class), Loading @@ -190,8 +193,11 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } @VisibleForTesting NotifyingTimeZoneChangeListener(Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor, NotificationManager notificationManager, NotifyingTimeZoneChangeListener( Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor, NotificationManager notificationManager, @NonNull Environment environment) { mHandler = Objects.requireNonNull(handler); mContext = Objects.requireNonNull(context); Loading @@ -213,8 +219,12 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { if (!mIsRegistered) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_NOTIFICATION_DELETED); mContext.registerReceiverForAllUsers(mNotificationReceiver, intentFilter, /* broadcastPermission= */ null, mHandler, RECEIVER_NOT_EXPORTED); mContext.registerReceiverForAllUsers( mNotificationReceiver, intentFilter, /* broadcastPermission= */ null, mHandler, RECEIVER_NOT_EXPORTED); mIsRegistered = true; } } else if (mIsRegistered) { Loading Loading @@ -266,8 +276,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { * zone change by manually changing the time zone within {@code AUTO_REVERT_THRESHOLD} of the * notification being received. */ private void markChangeAsAccepted(int changeEventId, @UserIdInt int userId, @SignalType int signalType) { private void markChangeAsAccepted( int changeEventId, @UserIdInt int userId, @SignalType int signalType) { if (!isUserIdCurrentUser(userId)) { return; } Loading Loading @@ -317,8 +327,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { * notification being received. */ @GuardedBy("mTimeZoneChangeRecord") private void markChangeAsRejected(int changeEventId, @UserIdInt int userId, @SignalType int signalType) { private void markChangeAsRejected( int changeEventId, @UserIdInt int userId, @SignalType int signalType) { if (!isUserIdCurrentUser(userId)) { return; } Loading Loading @@ -368,8 +378,10 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { TimeZoneChangeEvent lastChangeEvent = lastTimeZoneChangeRecord.getEvent(); if (shouldRejectChangeEvent(changeEvent, lastChangeEvent)) { markChangeAsRejected(lastTimeZoneChangeRecord.getId(), changeEvent.getUserId(), SIGNAL_TYPE_HEURISTIC); markChangeAsRejected( lastTimeZoneChangeRecord.getId(), changeEvent.getUserId(), SIGNAL_TYPE_HEURISTIC); } } Loading Loading @@ -455,17 +467,17 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } } private static boolean shouldRejectChangeEvent(TimeZoneChangeEvent changeEvent, TimeZoneChangeEvent lastChangeEvent) { private static boolean shouldRejectChangeEvent( TimeZoneChangeEvent changeEvent, TimeZoneChangeEvent lastChangeEvent) { return changeEvent.getOrigin() == ORIGIN_MANUAL && lastChangeEvent.getOrigin() != ORIGIN_MANUAL && (changeEvent.getElapsedRealtimeMillis() - lastChangeEvent.getElapsedRealtimeMillis() < AUTO_REVERT_THRESHOLD); - lastChangeEvent.getElapsedRealtimeMillis() < AUTO_REVERT_THRESHOLD); } private void scheduleChangeAcceptedHeuristicCallback( TimeZoneChangeRecord trackedChangeEvent, @DurationMillisLong long delayMillis) { TimeZoneChangeRecord trackedChangeEvent, @DurationMillisLong long delayMillis) { mHandler.postDelayed( () -> changeAcceptedTimeHeuristicCallback(trackedChangeEvent.getId()), delayMillis); } Loading @@ -478,12 +490,12 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } private void clearNotificationForUser(@UserIdInt int userId) { mNotificationManager.cancelAsUser(NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, UserHandle.of(userId)); mNotificationManager.cancelAsUser( NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, UserHandle.of(userId)); } private void notifyOfTimeZoneChange(@UserIdInt int userId, TimeZoneChangeRecord trackedChangeEvent) { private void notifyOfTimeZoneChange( @UserIdInt int userId, TimeZoneChangeRecord trackedChangeEvent) { TimeZoneChangeEvent changeEvent = trackedChangeEvent.getEvent(); if (!Flags.datetimeNotifications() || !areNotificationsEnabled()) { Loading @@ -493,7 +505,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { TimeZone oldTimeZone = TimeZone.getTimeZone(changeEvent.getOldZoneId()); TimeZone newTimeZone = TimeZone.getTimeZone(changeEvent.getNewZoneId()); long unixEpochTimeMillis = changeEvent.getUnixEpochTimeMillis(); boolean hasOffsetChanged = newTimeZone.getOffset(unixEpochTimeMillis) boolean hasOffsetChanged = newTimeZone.getOffset(unixEpochTimeMillis) == oldTimeZone.getOffset(unixEpochTimeMillis); if (hasOffsetChanged) { Loading @@ -507,17 +520,20 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { final CharSequence title = mRes.getString(R.string.time_zone_change_notification_title); final CharSequence body = getNotificationBody(newTimeZone, unixEpochTimeMillis); final Intent clickNotificationIntent = new Intent(ACTION_DATE_SETTINGS) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK final Intent clickNotificationIntent = new Intent(ACTION_DATE_SETTINGS) .addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); final Intent clearNotificationIntent = new Intent(ACTION_NOTIFICATION_DELETED) final Intent clearNotificationIntent = new Intent(ACTION_NOTIFICATION_DELETED) .putExtra(NOTIFICATION_INTENT_EXTRA_USER_ID, userId) .putExtra(NOTIFICATION_INTENT_EXTRA_CHANGE_ID, trackedChangeEvent.getId()); Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.TIME) Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.TIME) .setSmallIcon(R.drawable.btn_clock_material) .setStyle(new Notification.BigTextStyle().bigText(body)) .setOnlyAlertOnce(true) Loading @@ -525,14 +541,16 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { .setTicker(title) .setContentTitle(title) .setContentText(body) .setContentIntent(PendingIntent.getActivityAsUser( .setContentIntent( PendingIntent.getActivityAsUser( mContext, /* requestCode= */ 0, clickNotificationIntent, /* flags= */ FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, /* options= */ null, UserHandle.of(userId))) .setDeleteIntent(PendingIntent.getBroadcast( .setDeleteIntent( PendingIntent.getBroadcast( mContext, /* requestCode= */ 0, clearNotificationIntent, Loading @@ -540,8 +558,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { .setAutoCancel(true) // auto-clear notification on selection .build(); mNotificationManager.notifyAsUser(NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, notification, UserHandle.of(userId)); mNotificationManager.notifyAsUser( NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, notification, UserHandle.of(userId)); } private CharSequence getNotificationBody(TimeZone newTimeZone, long unixEpochTimeMillis) { Loading @@ -554,8 +572,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { return mRes.getString(R.string.time_zone_change_notification_body, newTime, newOffset); } private static String formatInZone(DateFormat timeFormat, TimeZone timeZone, long unixEpochTimeMillis) { private static String formatInZone( DateFormat timeFormat, TimeZone timeZone, long unixEpochTimeMillis) { timeFormat.setTimeZone(timeZone); return timeFormat.format(unixEpochTimeMillis); } Loading @@ -564,11 +582,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { public void dump(IndentingPrintWriter pw) { synchronized (mConfigurationLock) { pw.println("currentUserId=" + mConfigurationInternal.getUserId()); pw.println("notificationsEnabledBehavior=" pw.println( "notificationsEnabledBehavior=" + mConfigurationInternal.getNotificationsEnabledBehavior()); pw.println("notificationTrackingSupported=" pw.println( "notificationTrackingSupported=" + mConfigurationInternal.isNotificationTrackingSupported()); pw.println("manualChangeTrackingSupported=" pw.println( "manualChangeTrackingSupported=" + mConfigurationInternal.isManualChangeTrackingSupported()); } Loading Loading @@ -630,10 +651,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { @Override public String toString() { return "TrackedTimeZoneChangeEvent{" + "mId=" + mId + ", mEvent=" + mEvent + ", mStatus=" + mStatus + ", mSignalType=" + mSignalType + "mId=" + mId + ", mEvent=" + mEvent + ", mStatus=" + mStatus + ", mSignalType=" + mSignalType + '}'; } Loading services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java +65 −58 Original line number Diff line number Diff line Loading @@ -69,15 +69,12 @@ import java.time.InstantSource; import java.util.ArrayList; import java.util.List; /** * White-box unit tests for {@link NotifyingTimeZoneChangeListener}. */ /** White-box unit tests for {@link NotifyingTimeZoneChangeListener}. */ @RunWith(JUnitParamsRunner.class) @EnableFlags(Flags.FLAG_DATETIME_NOTIFICATIONS) public class NotifyingTimeZoneChangeListenerTest { @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); @Rule(order = 0) public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule(); Loading @@ -92,8 +89,7 @@ public class NotifyingTimeZoneChangeListenerTest { private static final String INTERACT_ACROSS_USERS_FULL_PERMISSION = "android.permission.INTERACT_ACROSS_USERS_FULL"; @Mock private Context mContext; @Mock private Context mContext; private UiAutomation mUiAutomation; private FakeNotificationManager mNotificationManager; Loading @@ -116,7 +112,8 @@ public class NotifyingTimeZoneChangeListenerTest { mHandlerThread.start(); mHandler = new TestHandler(mHandlerThread.getLooper()); ConfigurationInternal config = new ConfigurationInternal.Builder() ConfigurationInternal config = new ConfigurationInternal.Builder() .setUserId(mUid) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) Loading @@ -142,8 +139,13 @@ public class NotifyingTimeZoneChangeListenerTest { mNotificationManager = new FakeNotificationManager(mContext, InstantSource.system()); mTimeZoneChangeTracker = new NotifyingTimeZoneChangeListener(mHandler, mContext, mServiceConfigAccessor, mNotificationManager, mFakeEnvironment); mTimeZoneChangeTracker = new NotifyingTimeZoneChangeListener( mHandler, mContext, mServiceConfigAccessor, mNotificationManager, mFakeEnvironment); } @After Loading Loading @@ -175,8 +177,8 @@ public class NotifyingTimeZoneChangeListenerTest { mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); assertEquals(expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals( expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals(0, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(0); } Loading Loading @@ -204,8 +206,8 @@ public class NotifyingTimeZoneChangeListenerTest { mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); assertEquals(expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals( expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals(0, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(1); } Loading Loading @@ -520,7 +522,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableLocationTimeZoneDetection() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setAutoDetectionEnabledSetting(true) .setGeoDetectionFeatureSupported(true) .setGeoDetectionEnabledSetting(true) Loading @@ -532,7 +535,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableTelephonyTimeZoneDetection() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setAutoDetectionEnabledSetting(true) .setGeoDetectionEnabledSetting(false) .setTelephonyDetectionFeatureSupported(true) Loading @@ -545,7 +549,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableTimeZoneNotifications() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setNotificationsSupported(true) .setNotificationsTrackingSupported(true) .setNotificationsEnabledSetting(true) Loading @@ -558,7 +563,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableNotificationsWithManualChangeTracking() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setNotificationsSupported(true) .setNotificationsTrackingSupported(true) .setNotificationsEnabledSetting(true) Loading @@ -571,7 +577,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void disableTimeZoneAutoDetection() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setAutoDetectionEnabledSetting(false) .setGeoDetectionEnabledSetting(false) .build(); Loading Loading @@ -607,8 +614,8 @@ public class NotifyingTimeZoneChangeListenerTest { } @Override public void notifyAsUser(@Nullable String tag, int id, Notification notification, UserHandle user) { public void notifyAsUser( @Nullable String tag, int id, Notification notification, UserHandle user) { mNotifications.add(notification); } Loading Loading
services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java +141 −116 Original line number Diff line number Diff line Loading @@ -65,51 +65,47 @@ import java.time.Duration; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; /** * An implementation of {@link TimeZoneChangeListener} that fires notifications. */ /** An implementation of {@link TimeZoneChangeListener} that fires notifications. */ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { @IntDef({STATUS_UNKNOWN, STATUS_UNTRACKED, STATUS_REJECTED, STATUS_ACCEPTED, STATUS_SUPERSEDED}) @IntDef({STATUS_UNKNOWN, STATUS_UNTRACKED, STATUS_REJECTED, STATUS_ACCEPTED, STATUS_SUPERSEDED}) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TimeZoneChangeStatus {} /** Used to indicate the status could not be inferred. */ @TimeZoneChangeStatus static final int STATUS_UNKNOWN = 0; @TimeZoneChangeStatus static final int STATUS_UNKNOWN = 0; /** Used to indicate the change is not one that needs to be tracked. */ @TimeZoneChangeStatus static final int STATUS_UNTRACKED = 1; @TimeZoneChangeStatus static final int STATUS_REJECTED = 2; @TimeZoneChangeStatus static final int STATUS_ACCEPTED = 3; /** Used to indicate a change was superseded before its status could be determined. */ @TimeZoneChangeStatus static final int STATUS_SUPERSEDED = 4; @TimeZoneChangeStatus static final int STATUS_UNTRACKED = 1; @TimeZoneChangeStatus static final int STATUS_REJECTED = 2; @TimeZoneChangeStatus static final int STATUS_ACCEPTED = 3; @IntDef({SIGNAL_TYPE_UNKNOWN, SIGNAL_TYPE_NONE, SIGNAL_TYPE_NOTIFICATION, SIGNAL_TYPE_HEURISTIC}) /** Used to indicate a change was superseded before its status could be determined. */ @TimeZoneChangeStatus static final int STATUS_SUPERSEDED = 4; @IntDef({ SIGNAL_TYPE_UNKNOWN, SIGNAL_TYPE_NONE, SIGNAL_TYPE_NOTIFICATION, SIGNAL_TYPE_HEURISTIC }) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface SignalType {} /** Used when the signal type cannot be inferred. */ @SignalType static final int SIGNAL_TYPE_UNKNOWN = 0; @SignalType static final int SIGNAL_TYPE_UNKNOWN = 0; /** Used when the status is not one that needs a signal type. */ @SignalType static final int SIGNAL_TYPE_NONE = 1; @SignalType static final int SIGNAL_TYPE_NOTIFICATION = 2; @SignalType static final int SIGNAL_TYPE_HEURISTIC = 3; @SignalType static final int SIGNAL_TYPE_NONE = 1; @SignalType static final int SIGNAL_TYPE_NOTIFICATION = 2; @SignalType static final int SIGNAL_TYPE_HEURISTIC = 3; private static final int MAX_EVENTS_TO_TRACK = 10; @VisibleForTesting @DurationMillisLong @VisibleForTesting @DurationMillisLong static final long AUTO_REVERT_THRESHOLD = Duration.ofMinutes(15).toMillis(); private static final String TAG = "TimeZoneChangeTracker"; Loading Loading @@ -137,15 +133,18 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { private final ReferenceWithHistory<TimeZoneChangeRecord> mTimeZoneChangeRecord = new ReferenceWithHistory<>(MAX_EVENTS_TO_TRACK); private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_NOTIFICATION_DELETED: int notifiedUserId = intent.getIntExtra( NOTIFICATION_INTENT_EXTRA_USER_ID, UserHandle.USER_NULL); int changeEventId = intent.getIntExtra( NOTIFICATION_INTENT_EXTRA_CHANGE_ID, 0); int notifiedUserId = intent.getIntExtra( NOTIFICATION_INTENT_EXTRA_USER_ID, UserHandle.USER_NULL); int changeEventId = intent.getIntExtra(NOTIFICATION_INTENT_EXTRA_CHANGE_ID, 0); notificationSwipedAway(notifiedUserId, changeEventId); break; default: Loading @@ -154,12 +153,13 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } }; @NonNull private final Environment mEnvironment; @NonNull private final Environment mEnvironment; private final Object mConfigurationLock = new Object(); @GuardedBy("mConfigurationLock") private ConfigurationInternal mConfigurationInternal; @GuardedBy("mConfigurationLock") private boolean mIsRegistered; Loading @@ -173,11 +173,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { /** Create and initialise a new {@code TimeZoneChangeTrackerImpl} */ @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public static NotifyingTimeZoneChangeListener create(Handler handler, Context context, public static NotifyingTimeZoneChangeListener create( Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor, @NonNull Environment environment) { NotifyingTimeZoneChangeListener changeTracker = new NotifyingTimeZoneChangeListener(handler, new NotifyingTimeZoneChangeListener( handler, context, serviceConfigAccessor, context.getSystemService(NotificationManager.class), Loading @@ -190,8 +193,11 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } @VisibleForTesting NotifyingTimeZoneChangeListener(Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor, NotificationManager notificationManager, NotifyingTimeZoneChangeListener( Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor, NotificationManager notificationManager, @NonNull Environment environment) { mHandler = Objects.requireNonNull(handler); mContext = Objects.requireNonNull(context); Loading @@ -213,8 +219,12 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { if (!mIsRegistered) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_NOTIFICATION_DELETED); mContext.registerReceiverForAllUsers(mNotificationReceiver, intentFilter, /* broadcastPermission= */ null, mHandler, RECEIVER_NOT_EXPORTED); mContext.registerReceiverForAllUsers( mNotificationReceiver, intentFilter, /* broadcastPermission= */ null, mHandler, RECEIVER_NOT_EXPORTED); mIsRegistered = true; } } else if (mIsRegistered) { Loading Loading @@ -266,8 +276,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { * zone change by manually changing the time zone within {@code AUTO_REVERT_THRESHOLD} of the * notification being received. */ private void markChangeAsAccepted(int changeEventId, @UserIdInt int userId, @SignalType int signalType) { private void markChangeAsAccepted( int changeEventId, @UserIdInt int userId, @SignalType int signalType) { if (!isUserIdCurrentUser(userId)) { return; } Loading Loading @@ -317,8 +327,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { * notification being received. */ @GuardedBy("mTimeZoneChangeRecord") private void markChangeAsRejected(int changeEventId, @UserIdInt int userId, @SignalType int signalType) { private void markChangeAsRejected( int changeEventId, @UserIdInt int userId, @SignalType int signalType) { if (!isUserIdCurrentUser(userId)) { return; } Loading Loading @@ -368,8 +378,10 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { TimeZoneChangeEvent lastChangeEvent = lastTimeZoneChangeRecord.getEvent(); if (shouldRejectChangeEvent(changeEvent, lastChangeEvent)) { markChangeAsRejected(lastTimeZoneChangeRecord.getId(), changeEvent.getUserId(), SIGNAL_TYPE_HEURISTIC); markChangeAsRejected( lastTimeZoneChangeRecord.getId(), changeEvent.getUserId(), SIGNAL_TYPE_HEURISTIC); } } Loading Loading @@ -455,17 +467,17 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } } private static boolean shouldRejectChangeEvent(TimeZoneChangeEvent changeEvent, TimeZoneChangeEvent lastChangeEvent) { private static boolean shouldRejectChangeEvent( TimeZoneChangeEvent changeEvent, TimeZoneChangeEvent lastChangeEvent) { return changeEvent.getOrigin() == ORIGIN_MANUAL && lastChangeEvent.getOrigin() != ORIGIN_MANUAL && (changeEvent.getElapsedRealtimeMillis() - lastChangeEvent.getElapsedRealtimeMillis() < AUTO_REVERT_THRESHOLD); - lastChangeEvent.getElapsedRealtimeMillis() < AUTO_REVERT_THRESHOLD); } private void scheduleChangeAcceptedHeuristicCallback( TimeZoneChangeRecord trackedChangeEvent, @DurationMillisLong long delayMillis) { TimeZoneChangeRecord trackedChangeEvent, @DurationMillisLong long delayMillis) { mHandler.postDelayed( () -> changeAcceptedTimeHeuristicCallback(trackedChangeEvent.getId()), delayMillis); } Loading @@ -478,12 +490,12 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } private void clearNotificationForUser(@UserIdInt int userId) { mNotificationManager.cancelAsUser(NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, UserHandle.of(userId)); mNotificationManager.cancelAsUser( NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, UserHandle.of(userId)); } private void notifyOfTimeZoneChange(@UserIdInt int userId, TimeZoneChangeRecord trackedChangeEvent) { private void notifyOfTimeZoneChange( @UserIdInt int userId, TimeZoneChangeRecord trackedChangeEvent) { TimeZoneChangeEvent changeEvent = trackedChangeEvent.getEvent(); if (!Flags.datetimeNotifications() || !areNotificationsEnabled()) { Loading @@ -493,7 +505,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { TimeZone oldTimeZone = TimeZone.getTimeZone(changeEvent.getOldZoneId()); TimeZone newTimeZone = TimeZone.getTimeZone(changeEvent.getNewZoneId()); long unixEpochTimeMillis = changeEvent.getUnixEpochTimeMillis(); boolean hasOffsetChanged = newTimeZone.getOffset(unixEpochTimeMillis) boolean hasOffsetChanged = newTimeZone.getOffset(unixEpochTimeMillis) == oldTimeZone.getOffset(unixEpochTimeMillis); if (hasOffsetChanged) { Loading @@ -507,17 +520,20 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { final CharSequence title = mRes.getString(R.string.time_zone_change_notification_title); final CharSequence body = getNotificationBody(newTimeZone, unixEpochTimeMillis); final Intent clickNotificationIntent = new Intent(ACTION_DATE_SETTINGS) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK final Intent clickNotificationIntent = new Intent(ACTION_DATE_SETTINGS) .addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); final Intent clearNotificationIntent = new Intent(ACTION_NOTIFICATION_DELETED) final Intent clearNotificationIntent = new Intent(ACTION_NOTIFICATION_DELETED) .putExtra(NOTIFICATION_INTENT_EXTRA_USER_ID, userId) .putExtra(NOTIFICATION_INTENT_EXTRA_CHANGE_ID, trackedChangeEvent.getId()); Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.TIME) Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.TIME) .setSmallIcon(R.drawable.btn_clock_material) .setStyle(new Notification.BigTextStyle().bigText(body)) .setOnlyAlertOnce(true) Loading @@ -525,14 +541,16 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { .setTicker(title) .setContentTitle(title) .setContentText(body) .setContentIntent(PendingIntent.getActivityAsUser( .setContentIntent( PendingIntent.getActivityAsUser( mContext, /* requestCode= */ 0, clickNotificationIntent, /* flags= */ FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, /* options= */ null, UserHandle.of(userId))) .setDeleteIntent(PendingIntent.getBroadcast( .setDeleteIntent( PendingIntent.getBroadcast( mContext, /* requestCode= */ 0, clearNotificationIntent, Loading @@ -540,8 +558,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { .setAutoCancel(true) // auto-clear notification on selection .build(); mNotificationManager.notifyAsUser(NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, notification, UserHandle.of(userId)); mNotificationManager.notifyAsUser( NOTIFICATION_TAG, TZ_CHANGE_NOTIFICATION_ID, notification, UserHandle.of(userId)); } private CharSequence getNotificationBody(TimeZone newTimeZone, long unixEpochTimeMillis) { Loading @@ -554,8 +572,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { return mRes.getString(R.string.time_zone_change_notification_body, newTime, newOffset); } private static String formatInZone(DateFormat timeFormat, TimeZone timeZone, long unixEpochTimeMillis) { private static String formatInZone( DateFormat timeFormat, TimeZone timeZone, long unixEpochTimeMillis) { timeFormat.setTimeZone(timeZone); return timeFormat.format(unixEpochTimeMillis); } Loading @@ -564,11 +582,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { public void dump(IndentingPrintWriter pw) { synchronized (mConfigurationLock) { pw.println("currentUserId=" + mConfigurationInternal.getUserId()); pw.println("notificationsEnabledBehavior=" pw.println( "notificationsEnabledBehavior=" + mConfigurationInternal.getNotificationsEnabledBehavior()); pw.println("notificationTrackingSupported=" pw.println( "notificationTrackingSupported=" + mConfigurationInternal.isNotificationTrackingSupported()); pw.println("manualChangeTrackingSupported=" pw.println( "manualChangeTrackingSupported=" + mConfigurationInternal.isManualChangeTrackingSupported()); } Loading Loading @@ -630,10 +651,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { @Override public String toString() { return "TrackedTimeZoneChangeEvent{" + "mId=" + mId + ", mEvent=" + mEvent + ", mStatus=" + mStatus + ", mSignalType=" + mSignalType + "mId=" + mId + ", mEvent=" + mEvent + ", mStatus=" + mStatus + ", mSignalType=" + mSignalType + '}'; } Loading
services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java +65 −58 Original line number Diff line number Diff line Loading @@ -69,15 +69,12 @@ import java.time.InstantSource; import java.util.ArrayList; import java.util.List; /** * White-box unit tests for {@link NotifyingTimeZoneChangeListener}. */ /** White-box unit tests for {@link NotifyingTimeZoneChangeListener}. */ @RunWith(JUnitParamsRunner.class) @EnableFlags(Flags.FLAG_DATETIME_NOTIFICATIONS) public class NotifyingTimeZoneChangeListenerTest { @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); @Rule(order = 0) public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule(); Loading @@ -92,8 +89,7 @@ public class NotifyingTimeZoneChangeListenerTest { private static final String INTERACT_ACROSS_USERS_FULL_PERMISSION = "android.permission.INTERACT_ACROSS_USERS_FULL"; @Mock private Context mContext; @Mock private Context mContext; private UiAutomation mUiAutomation; private FakeNotificationManager mNotificationManager; Loading @@ -116,7 +112,8 @@ public class NotifyingTimeZoneChangeListenerTest { mHandlerThread.start(); mHandler = new TestHandler(mHandlerThread.getLooper()); ConfigurationInternal config = new ConfigurationInternal.Builder() ConfigurationInternal config = new ConfigurationInternal.Builder() .setUserId(mUid) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) Loading @@ -142,8 +139,13 @@ public class NotifyingTimeZoneChangeListenerTest { mNotificationManager = new FakeNotificationManager(mContext, InstantSource.system()); mTimeZoneChangeTracker = new NotifyingTimeZoneChangeListener(mHandler, mContext, mServiceConfigAccessor, mNotificationManager, mFakeEnvironment); mTimeZoneChangeTracker = new NotifyingTimeZoneChangeListener( mHandler, mContext, mServiceConfigAccessor, mNotificationManager, mFakeEnvironment); } @After Loading Loading @@ -175,8 +177,8 @@ public class NotifyingTimeZoneChangeListenerTest { mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); assertEquals(expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals( expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals(0, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(0); } Loading Loading @@ -204,8 +206,8 @@ public class NotifyingTimeZoneChangeListenerTest { mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); assertEquals(expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals( expectedTimeZoneChangeRecord, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals(0, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(1); } Loading Loading @@ -520,7 +522,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableLocationTimeZoneDetection() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setAutoDetectionEnabledSetting(true) .setGeoDetectionFeatureSupported(true) .setGeoDetectionEnabledSetting(true) Loading @@ -532,7 +535,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableTelephonyTimeZoneDetection() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setAutoDetectionEnabledSetting(true) .setGeoDetectionEnabledSetting(false) .setTelephonyDetectionFeatureSupported(true) Loading @@ -545,7 +549,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableTimeZoneNotifications() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setNotificationsSupported(true) .setNotificationsTrackingSupported(true) .setNotificationsEnabledSetting(true) Loading @@ -558,7 +563,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void enableNotificationsWithManualChangeTracking() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setNotificationsSupported(true) .setNotificationsTrackingSupported(true) .setNotificationsEnabledSetting(true) Loading @@ -571,7 +577,8 @@ public class NotifyingTimeZoneChangeListenerTest { private void disableTimeZoneAutoDetection() { ConfigurationInternal oldConfiguration = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) ConfigurationInternal newConfiguration = toBuilder(oldConfiguration) .setAutoDetectionEnabledSetting(false) .setGeoDetectionEnabledSetting(false) .build(); Loading Loading @@ -607,8 +614,8 @@ public class NotifyingTimeZoneChangeListenerTest { } @Override public void notifyAsUser(@Nullable String tag, int id, Notification notification, UserHandle user) { public void notifyAsUser( @Nullable String tag, int id, Notification notification, UserHandle user) { mNotifications.add(notification); } Loading