Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 503a9f76 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Defer time zone acceptance heuristic until device unlock." into main

parents d9d3906d ff161ed8
Loading
Loading
Loading
Loading
+65 −7
Original line number Diff line number Diff line
@@ -31,9 +31,11 @@ import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -121,6 +123,7 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
    private final Context mContext;
    private final NotificationManager mNotificationManager;
    private final ActivityManagerInternal mActivityManagerInternal;
    private final KeyguardManager mKeyguardManager;

    // For scheduling callbacks
    private final Handler mHandler;
@@ -163,6 +166,10 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
    @GuardedBy("mConfigurationLock")
    private boolean mIsRegistered;

    @VisibleForTesting
    @GuardedBy("mConfigurationLock")
    UserPresentReceiver mUserPresentReceiver;

    private int mAcceptedManualChanges;
    private int mAcceptedTelephonyChanges;
    private int mAcceptedLocationChanges;
@@ -184,7 +191,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
                        context,
                        serviceConfigAccessor,
                        context.getSystemService(NotificationManager.class),
                        environment);
                        environment,
                        context.getSystemService(KeyguardManager.class));

        // Pretend there was an update to initialize configuration.
        changeTracker.handleConfigurationUpdate();
@@ -198,7 +206,8 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
            Context context,
            ServiceConfigAccessor serviceConfigAccessor,
            NotificationManager notificationManager,
            @NonNull Environment environment) {
            @NonNull Environment environment,
            KeyguardManager keyguardManager) {
        mHandler = Objects.requireNonNull(handler);
        mContext = Objects.requireNonNull(context);
        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
@@ -207,6 +216,7 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
        mNotificationManager = notificationManager;
        mEnvironment = Objects.requireNonNull(environment);
        mKeyguardManager = keyguardManager;
    }

    @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
@@ -387,7 +397,7 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {

                // Schedule a callback for the new time zone so that we can implement "user accepted
                // the change because they didn't revert it"
                scheduleChangeAcceptedHeuristicCallback(trackedChangeEvent, AUTO_REVERT_THRESHOLD);
                scheduleChangeAcceptedHeuristicCallback(trackedChangeEvent.getId());
            }

            if (lastTimeZoneChangeRecord != null
@@ -476,10 +486,58 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
                        < AUTO_REVERT_THRESHOLD);
    }

    private void scheduleChangeAcceptedHeuristicCallback(
            TimeZoneChangeRecord trackedChangeEvent, @DurationMillisLong long delayMillis) {
    private void scheduleChangeAcceptedHeuristicCallback(int trackedChangeEventId) {
        if (!android.timezone.flags.Flags.enableAutomaticTimeZoneRejectionLogging()) {
            mHandler.postDelayed(
                    () -> changeAcceptedTimeHeuristicCallback(trackedChangeEventId),
                    AUTO_REVERT_THRESHOLD);
            return;
        }
        if (mKeyguardManager == null || !mKeyguardManager.isDeviceLocked()) {
            mHandler.postDelayed(
                () -> changeAcceptedTimeHeuristicCallback(trackedChangeEvent.getId()), delayMillis);
                    () -> changeAcceptedTimeHeuristicCallback(trackedChangeEventId),
                    AUTO_REVERT_THRESHOLD);
            return;
        }
        resetUserPresentReceiver(new UserPresentReceiver(trackedChangeEventId));
    }

    // Registering receiver to wait until device is unlocked.
    private void resetUserPresentReceiver(@Nullable UserPresentReceiver userPresentReceiver) {
        synchronized (mConfigurationLock) {
            if (mUserPresentReceiver != null) {
                try {
                    mContext.unregisterReceiver(mUserPresentReceiver);
                } catch (IllegalArgumentException e) {
                    // Handle the case where the receiver might have already been unregistered
                }
            }
            mUserPresentReceiver = userPresentReceiver;
            if (mUserPresentReceiver != null) {
                IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
                mContext.registerReceiver(mUserPresentReceiver, filter);
            }
        }
    }

    // BroadcastReceiver to listen for ACTION_USER_PRESENT.
    @VisibleForTesting
    class UserPresentReceiver extends BroadcastReceiver {
        private final int trackedChangeEventId;

        public UserPresentReceiver(int trackedChangeEventId) {
            this.trackedChangeEventId = trackedChangeEventId;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
                mHandler.postDelayed(
                        () -> changeAcceptedTimeHeuristicCallback(trackedChangeEventId),
                        AUTO_REVERT_THRESHOLD);
                resetUserPresentReceiver(null);
            }
        }
    }

    private void changeAcceptedTimeHeuristicCallback(int changeEventId) {
+68 −2
Original line number Diff line number Diff line
@@ -32,14 +32,19 @@ import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.os.HandlerThread;
import android.os.Process;
import android.os.UserHandle;
@@ -62,6 +67,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

@@ -89,7 +95,7 @@ public class NotifyingTimeZoneChangeListenerTest {
    private static final String INTERACT_ACROSS_USERS_FULL_PERMISSION =
            "android.permission.INTERACT_ACROSS_USERS_FULL";

    @Mock private Context mContext;
    private Context mContext;
    private UiAutomation mUiAutomation;

    private FakeNotificationManager mNotificationManager;
@@ -99,6 +105,8 @@ public class NotifyingTimeZoneChangeListenerTest {
    private FakeEnvironment mFakeEnvironment;
    private int mUid;

    @Mock private KeyguardManager mockKeyguardManager;

    private NotifyingTimeZoneChangeListener mTimeZoneChangeTracker;

    @Before
@@ -134,6 +142,7 @@ public class NotifyingTimeZoneChangeListenerTest {
        mServiceConfigAccessor.initializeCurrentUserConfiguration(config);

        mContext = InstrumentationRegistry.getInstrumentation().getContext();

        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
        mUiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_USERS_FULL_PERMISSION);

@@ -145,7 +154,8 @@ public class NotifyingTimeZoneChangeListenerTest {
                        mContext,
                        mServiceConfigAccessor,
                        mNotificationManager,
                        mFakeEnvironment);
                        mFakeEnvironment,
                        mockKeyguardManager);
    }

    @After
@@ -519,6 +529,62 @@ public class NotifyingTimeZoneChangeListenerTest {
        mHandler.assertTotalMessagesEnqueued(2);
    }

    @Test
    @EnableFlags(android.timezone.flags.Flags.FLAG_ENABLE_AUTOMATIC_TIME_ZONE_REJECTION_LOGGING)
    public void process_automaticDetection_deviceLocked_defersHeuristic() {
        enableNotificationsWithManualChangeTracking();
        Mockito.when(mockKeyguardManager.isDeviceLocked()).thenReturn(true);

        TimeZoneChangeEvent event =
                new TimeZoneChangeEvent(
                        /* elapsedRealtimeMillis= */ 0,
                        /* unixEpochTimeMillis= */ 1726597800000L,
                        /* origin= */ ORIGIN_TELEPHONY,
                        /* userId= */ mUid,
                        /* oldZoneId= */ "Europe/Paris",
                        /* newZoneId= */ "Europe/London",
                        /* oldConfidence= */ TIME_ZONE_CONFIDENCE_HIGH,
                        /* newConfidence= */ TIME_ZONE_CONFIDENCE_HIGH,
                        /* cause= */ "NO_REASON");

        mTimeZoneChangeTracker.process(event);

        // Verify that the heuristic callback is NOT posted immediately.
        mHandler.assertTotalMessagesEnqueued(0);

        // Simulate unlocking the device.
        Intent userPresentIntent = new Intent(Intent.ACTION_USER_PRESENT);
        mTimeZoneChangeTracker.mUserPresentReceiver.onReceive(mContext, userPresentIntent);

        // Now, the handler message should be enqueued.
        mHandler.assertTotalMessagesEnqueued(1);

    }

    @Test
    @EnableFlags(android.timezone.flags.Flags.FLAG_ENABLE_AUTOMATIC_TIME_ZONE_REJECTION_LOGGING)
    public void process_automaticDetection_deviceUnlocked_notDefersHeuristic() {
        enableNotificationsWithManualChangeTracking();
        Mockito.when(mockKeyguardManager.isDeviceLocked()).thenReturn(false);

        TimeZoneChangeEvent event =
                new TimeZoneChangeEvent(
                        /* elapsedRealtimeMillis= */ 0,
                        /* unixEpochTimeMillis= */ 1726597800000L,
                        /* origin= */ ORIGIN_TELEPHONY,
                        /* userId= */ mUid,
                        /* oldZoneId= */ "Europe/Paris",
                        /* newZoneId= */ "Europe/London",
                        /* oldConfidence= */ TIME_ZONE_CONFIDENCE_HIGH,
                        /* newConfidence= */ TIME_ZONE_CONFIDENCE_HIGH,
                        /* cause= */ "NO_REASON");

        mTimeZoneChangeTracker.process(event);

        // Verify that the heuristic callback is posted immediately.
        mHandler.assertTotalMessagesEnqueued(1);
    }

    private void enableLocationTimeZoneDetection() {
        ConfigurationInternal oldConfiguration =
                mServiceConfigAccessor.getCurrentUserConfigurationInternal();