From c3b2165dfe54a44b012456541a72628f0408bdf8 Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Fri, 31 May 2024 20:54:55 +0000 Subject: [PATCH 1/6] Update A11ySettings to load preferences in onCreate(). This version still only loads preferences once, but the previous attempt commit 774bbc1ff29eec2f1eb8ce654917dcb651bbbd74 created a "sliding" effect as the preferences were loaded after the page became visible to the user. Also reorders methods so that their position in the source file matches the Activity lifecycle ordering. Bug: 327052480 Test: existing A11y Settings robotest presubmit Test: Launch this page from the Settings app, observe no sliding UI Flag: NONE low risk visual bug fix Change-Id: I44312ada359aef7dec8eb27c57cde2a8e00f254b --- .../accessibility/AccessibilitySettings.java | 19 +++++++++++++------ .../AccessibilitySettingsTest.java | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 4e77243b09d..b5df0e22c80 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -210,24 +210,31 @@ public class AccessibilitySettings extends DashboardFragment implements public void onCreate(Bundle icicle) { super.onCreate(icicle); initializeAllPreferences(); + updateAllPreferences(); + mNeedPreferencesUpdate = false; registerContentMonitors(); registerInputDeviceListener(); } @Override - public void onResume() { - super.onResume(); - updateAllPreferences(); + public void onStart() { + super.onStart(); + mIsForeground = true; } @Override - public void onStart() { + public void onResume() { + super.onResume(); if (mNeedPreferencesUpdate) { updateAllPreferences(); mNeedPreferencesUpdate = false; } - mIsForeground = true; - super.onStart(); + } + + @Override + public void onPause() { + super.onPause(); + mNeedPreferencesUpdate = true; } @Override diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 21ff6c7266b..1463cd0b7f9 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -385,6 +385,7 @@ public class AccessibilitySettingsTest { mFragment.onContentChanged(); mFragment.onStart(); + mFragment.onResume(); RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference( COMPONENT_NAME.flattenToString()); From 5ce2589dba2a07f12aefae64097dcb3d8b510e01 Mon Sep 17 00:00:00 2001 From: chelseahao Date: Mon, 3 Jun 2024 15:53:58 +0800 Subject: [PATCH 2/6] [Audiosharing] Clean up some tests - properly reset shadows and removed unused spy. Test: atest -c com.android.settings.connecteddevice.audiosharing.audiostreams Bug: 308368124 Change-Id: If47a00ec02fbd78146483d5a409d9bcc42b5e841 --- .../AudioStreamButtonControllerTest.java | 9 +++++++-- .../AudioStreamHeaderControllerTest.java | 10 ++++++++-- .../audiostreams/AudioStreamPreferenceTest.java | 5 ++--- .../AudioStreamsActiveDeviceControllerTest.java | 4 ++-- ...ioStreamsActiveDeviceSummaryUpdaterTest.java | 10 +++++++--- .../audiostreams/MediaControlHelperTest.java | 13 ++++++++++--- .../audiostreams/SyncedStateTest.java | 9 +++++++-- .../testshadows/ShadowAudioStreamsHelper.java | 17 +++++++++++------ .../ShadowEntityHeaderController.java | 7 +++++++ .../testshadows/ShadowLocalMediaManager.java | 10 +++++++++- 10 files changed, 70 insertions(+), 24 deletions(-) diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java index adcc6172b92..cbf14321e4f 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java @@ -34,12 +34,12 @@ import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadow import com.android.settings.testutils.shadow.ShadowThreadUtils; import com.android.settingslib.widget.ActionButtonsPreference; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @@ -59,7 +59,7 @@ public class AudioStreamButtonControllerTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); private static final String KEY = "audio_stream_button"; private static final int BROADCAST_ID = 1; - @Spy Context mContext = ApplicationProvider.getApplicationContext(); + private final Context mContext = ApplicationProvider.getApplicationContext(); @Mock private AudioStreamsHelper mAudioStreamsHelper; @Mock private PreferenceScreen mScreen; @Mock private BluetoothLeBroadcastReceiveState mBroadcastReceiveState; @@ -80,6 +80,11 @@ public class AudioStreamButtonControllerTest { .thenReturn(mPreference); } + @After + public void tearDown() { + ShadowAudioStreamsHelper.reset(); + } + @Test public void testDisplayPreference_sourceConnected_setDisconnectButton() { when(mAudioStreamsHelper.getAllConnectedSources()) diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java index 0af9c17543b..0cd5d61168b 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java @@ -34,12 +34,12 @@ import com.android.settings.testutils.shadow.ShadowThreadUtils; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.widget.LayoutPreference; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @@ -61,7 +61,7 @@ public class AudioStreamHeaderControllerTest { private static final String KEY = "audio_stream_header"; private static final int BROADCAST_ID = 1; private static final String BROADCAST_NAME = "broadcast name"; - @Spy Context mContext = ApplicationProvider.getApplicationContext(); + private final Context mContext = ApplicationProvider.getApplicationContext(); @Mock private AudioStreamsHelper mAudioStreamsHelper; @Mock private PreferenceScreen mScreen; @Mock private BluetoothLeBroadcastReceiveState mBroadcastReceiveState; @@ -81,6 +81,12 @@ public class AudioStreamHeaderControllerTest { when(mPreference.getContext()).thenReturn(mContext); } + @After + public void tearDown() { + ShadowEntityHeaderController.reset(); + ShadowAudioStreamsHelper.reset(); + } + @Test public void testDisplayPreference_sourceConnected_setSummary() { when(mAudioStreamsHelper.getAllConnectedSources()) diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreferenceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreferenceTest.java index 0c93e3e1879..456e45d302a 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreferenceTest.java @@ -30,6 +30,7 @@ import android.view.View; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceViewHolder; +import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState; @@ -42,7 +43,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import java.util.Collections; @@ -53,7 +53,7 @@ public class AudioStreamPreferenceTest { private static final String PROGRAM_NAME = "program_name"; private static final int BROADCAST_RSSI = 1; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); - private Context mContext; + private final Context mContext = ApplicationProvider.getApplicationContext(); private AudioStreamPreference mPreference; @Mock private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; @Mock private BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState; @@ -61,7 +61,6 @@ public class AudioStreamPreferenceTest { @Before public void setUp() { - mContext = RuntimeEnvironment.application; mPreference = new AudioStreamPreference(mContext, null); when(mBluetoothLeBroadcastMetadata.getBroadcastId()).thenReturn(BROADCAST_ID); when(mBluetoothLeBroadcastMetadata.getBroadcastName()).thenReturn(BROADCAST_NAME); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceControllerTest.java index c0296350db4..3fd257f100c 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceControllerTest.java @@ -28,6 +28,7 @@ import android.content.Context; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; import org.junit.Before; import org.junit.Rule; @@ -37,7 +38,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class AudioStreamsActiveDeviceControllerTest { @@ -48,7 +48,7 @@ public class AudioStreamsActiveDeviceControllerTest { @Before public void setUp() { - Context context = RuntimeEnvironment.application; + Context context = ApplicationProvider.getApplicationContext(); mController = new AudioStreamsActiveDeviceController( context, AudioStreamsActiveDeviceController.KEY); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java index 3bcc9a3d079..4403528e2fb 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java @@ -30,12 +30,12 @@ import com.android.settings.R; import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @@ -49,7 +49,7 @@ import org.robolectric.annotation.Config; public class AudioStreamsActiveDeviceSummaryUpdaterTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); private static final String DEVICE_NAME = "device_name"; - @Spy private final Context mContext = ApplicationProvider.getApplicationContext(); + private final Context mContext = ApplicationProvider.getApplicationContext(); private final AudioStreamsActiveDeviceSummaryUpdater.OnSummaryChangeListener mFakeListener = summary -> mUpdatedSummary = summary; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; @@ -60,10 +60,14 @@ public class AudioStreamsActiveDeviceSummaryUpdaterTest { @Before public void setUp() { ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper); - ShadowAudioStreamsHelper.resetCachedBluetoothDevice(); mUpdater = new AudioStreamsActiveDeviceSummaryUpdater(mContext, mFakeListener); } + @After + public void tearDown() { + ShadowAudioStreamsHelper.reset(); + } + @Test public void register_summaryUpdated() { mUpdater.register(true); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/MediaControlHelperTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/MediaControlHelperTest.java index 113fc722284..2506d863a56 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/MediaControlHelperTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/MediaControlHelperTest.java @@ -23,6 +23,8 @@ import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; +import androidx.test.core.app.ApplicationProvider; + import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper; import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowLocalMediaManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -30,6 +32,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.LocalMediaManager; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -38,7 +41,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.util.List; @@ -64,15 +66,20 @@ public class MediaControlHelperTest { @Before public void setUp() { - mContext = spy(RuntimeEnvironment.application); + mContext = spy(ApplicationProvider.getApplicationContext()); when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mMediaSessionManager); when(mMediaSessionManager.getActiveSessions(any())).thenReturn(List.of(mMediaController)); when(mMediaController.getPackageName()).thenReturn(FAKE_PACKAGE); when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState); - ShadowAudioStreamsHelper.resetCachedBluetoothDevice(); ShadowLocalMediaManager.setUseMock(mLocalMediaManager); } + @After + public void tearDown() { + ShadowAudioStreamsHelper.reset(); + ShadowLocalMediaManager.reset(); + } + @Test public void testStart_noBluetoothManager_doNothing() { MediaControlHelper helper = new MediaControlHelper(mContext, null); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java index 64e1bc48808..e9eab5066ac 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java @@ -21,7 +21,6 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; @@ -33,6 +32,7 @@ import android.content.Context; import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -60,10 +60,15 @@ public class SyncedStateTest { @Before public void setUp() { ShadowAlertDialog.reset(); - mMockContext = spy(ApplicationProvider.getApplicationContext()); + mMockContext = ApplicationProvider.getApplicationContext(); mInstance = SyncedState.getInstance(); } + @After + public void tearDown() { + ShadowAlertDialog.reset(); + } + @Test public void testGetInstance() { assertThat(mInstance).isNotNull(); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java index 0dff64d99de..331a30bec20 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java @@ -18,12 +18,15 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams.testshado import android.bluetooth.BluetoothLeBroadcastReceiveState; +import androidx.annotation.Nullable; + import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsHelper; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import java.util.List; import java.util.Optional; @@ -31,20 +34,22 @@ import java.util.Optional; @Implements(value = AudioStreamsHelper.class, callThroughByDefault = false) public class ShadowAudioStreamsHelper { private static AudioStreamsHelper sMockHelper; - private static Optional sCachedBluetoothDevice; + @Nullable private static CachedBluetoothDevice sCachedBluetoothDevice; public static void setUseMock(AudioStreamsHelper mockAudioStreamsHelper) { sMockHelper = mockAudioStreamsHelper; } - /** Resets {@link CachedBluetoothDevice} */ - public static void resetCachedBluetoothDevice() { - sCachedBluetoothDevice = Optional.empty(); + /** Reset static fields */ + @Resetter + public static void reset() { + sMockHelper = null; + sCachedBluetoothDevice = null; } public static void setCachedBluetoothDeviceInSharingOrLeConnected( CachedBluetoothDevice cachedBluetoothDevice) { - sCachedBluetoothDevice = Optional.of(cachedBluetoothDevice); + sCachedBluetoothDevice = cachedBluetoothDevice; } @Implementation @@ -56,6 +61,6 @@ public class ShadowAudioStreamsHelper { @Implementation public static Optional getCachedBluetoothDeviceInSharingOrLeConnected( LocalBluetoothManager manager) { - return sCachedBluetoothDevice; + return Optional.ofNullable(sCachedBluetoothDevice); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowEntityHeaderController.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowEntityHeaderController.java index 951fb26c252..82519c5e8a4 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowEntityHeaderController.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowEntityHeaderController.java @@ -25,6 +25,7 @@ import com.android.settings.widget.EntityHeaderController; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; @Implements(value = EntityHeaderController.class, callThroughByDefault = false) public class ShadowEntityHeaderController { @@ -34,6 +35,12 @@ public class ShadowEntityHeaderController { sMockController = mockController; } + /** Reset static fields */ + @Resetter + public static void reset() { + sMockController = null; + } + /** Returns new instance of {@link EntityHeaderController} */ @Implementation public static EntityHeaderController newInstance( diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowLocalMediaManager.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowLocalMediaManager.java index 02f12c20350..4c679fb81f5 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowLocalMediaManager.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowLocalMediaManager.java @@ -21,6 +21,7 @@ import com.android.settingslib.media.MediaDevice; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import java.util.Collections; @@ -34,6 +35,13 @@ public class ShadowLocalMediaManager { sMockManager = mockLocalMediaManager; } + /** Reset static fields */ + @Resetter + public static void reset() { + sMockManager = null; + sDeviceCallback = null; + } + /** Triggers onDeviceListUpdate of {@link LocalMediaManager.DeviceCallback} */ public static void onDeviceListUpdate() { sDeviceCallback.onDeviceListUpdate(Collections.emptyList()); @@ -45,7 +53,7 @@ public class ShadowLocalMediaManager { sMockManager.startScan(); } - /** Registers {@link LocalMediaManager.DeviceCallback} */ + /** Registers {@link LocalMediaManager.DeviceCallback} */ @Implementation public void registerCallback(LocalMediaManager.DeviceCallback deviceCallback) { sMockManager.registerCallback(deviceCallback); From 320c4c6f552bb99f9bee920629274a5743f9ed3b Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Thu, 30 May 2024 16:20:13 +0800 Subject: [PATCH 3/6] Dump app optimization mode expiration event data in bug report. Bug: 338965652 Test: atest + manual Change-Id: Id506fce6c3bc1271be2677216fc4b1cfe6ada6d0 --- .../batteryusage/bugreport/BugReportContentProvider.java | 1 + .../fuelgauge/batteryusage/bugreport/LogUtils.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java index ff953e77f86..7613d9ab097 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java +++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java @@ -54,6 +54,7 @@ public final class BugReportContentProvider extends ContentProvider { return; } writer.println("dump BatteryUsage and AppUsage states:"); + LogUtils.dumpAppOptimizationModeEventHist(context, writer); LogUtils.dumpBatteryUsageDatabaseHist(context, writer); LogUtils.dumpAppUsageDatabaseHist(context, writer); LogUtils.dumpBatteryUsageSlotDatabaseHist(context, writer); diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java index 88bd4adf463..b2300308fd4 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java @@ -20,6 +20,8 @@ import android.content.Context; import android.util.Log; import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; +import com.android.settings.fuelgauge.batteryusage.AppOptimizationModeEvent; import com.android.settings.fuelgauge.batteryusage.BatteryUsageSlot; import com.android.settings.fuelgauge.batteryusage.ConvertUtils; import com.android.settings.fuelgauge.batteryusage.DatabaseUtils; @@ -47,6 +49,13 @@ public final class LogUtils { private static final Duration DUMP_TIME_OFFSET = Duration.ofHours(24); private static final Duration DUMP_TIME_OFFSET_FOR_ENTRY = Duration.ofHours(4); + static void dumpAppOptimizationModeEventHist(Context context, PrintWriter writer) { + writer.println("\n\tApp Optimization Mode Event History:"); + final List events = + AppOptModeSharedPreferencesUtils.getAllEvents(context); + dumpListItems(writer, events, event -> event); + } + static void dumpBatteryUsageDatabaseHist(Context context, PrintWriter writer) { // Dumps periodic job events. writer.println("\nBattery PeriodicJob History:"); From 8eaf8edf7e01811c73fdb6b883a101437d7d5610 Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Thu, 30 May 2024 16:25:24 +0800 Subject: [PATCH 4/6] Add AppOptimizationModeEventsUtils to save & update app optimization mode expiration events. - [Update] Save app optimizaiton mode set & expire events from turbo. - [Reset ] Restore optimization mode for expired events in Periodic job. - [Delete] Cancel expiration event if user updates mode in app usage page. Bug: 338965652 Test: atest + manual Change-Id: I3fb7311207da1bdb1146ea1ff041aca6adb66052 --- Android.bp | 6 +- protos/fuelgauge_log.proto | 1 + .../fuelgauge/AdvancedPowerUsageDetail.java | 6 +- .../fuelgauge/BatteryOptimizeUtils.java | 12 +- .../fuelgauge/PowerBackgroundUsageDetail.java | 5 + .../AppOptModeSharedPreferencesUtils.kt | 223 ++++++++++++++++++ .../batteryusage/BatteryUsageDataLoader.java | 2 + .../settings/fuelgauge/protos/Android.bp | 36 +-- .../protos/app_optimization_mode_event.proto | 18 ++ .../AppOptModeSharedPreferencesUtilsTest.kt | 212 +++++++++++++++++ 10 files changed, 477 insertions(+), 44 deletions(-) create mode 100644 src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt create mode 100644 src/com/android/settings/fuelgauge/protos/app_optimization_mode_event.proto create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt diff --git a/Android.bp b/Android.bp index 06ce8ab22f5..b96d0dc2928 100644 --- a/Android.bp +++ b/Android.bp @@ -95,15 +95,11 @@ android_library { "SettingsLibActivityEmbedding", "aconfig_settings_flags_lib", "accessibility_settings_flags_lib", - "app-usage-event-protos-lite", - "battery-event-protos-lite", - "battery-usage-slot-protos-lite", "contextualcards", "development_settings_flag_lib", "factory_reset_flags_lib", "fuelgauge-log-protos-lite", - "fuelgauge-usage-state-protos-lite", - "power-anomaly-event-protos-lite", + "fuelgauge-protos-lite", "settings-contextual-card-protos-lite", "settings-log-bridge-protos-lite", "settings-logtags", diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto index b16958d8e2b..3be173e87ca 100644 --- a/protos/fuelgauge_log.proto +++ b/protos/fuelgauge_log.proto @@ -21,6 +21,7 @@ message BatteryOptimizeHistoricalLogEntry { BACKUP = 5; FORCE_RESET = 6; EXTERNAL_UPDATE = 7; + EXPIRATION_RESET = 8; } optional string package_name = 1; diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 42e6d9c4b68..005c0730343 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -43,6 +43,7 @@ import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; +import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry; import com.android.settings.fuelgauge.batteryusage.BatteryEntry; import com.android.settings.overlay.FeatureFactory; @@ -274,9 +275,12 @@ public class AdvancedPowerUsageDetail extends DashboardFragment final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode(); mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode); logMetricCategory(currentOptimizeMode); - mExecutor.execute( () -> { + if (currentOptimizeMode != mOptimizationMode) { + AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid( + getContext(), mBatteryOptimizeUtils.getUid()); + } BatteryOptimizeLogUtils.writeLog( getContext().getApplicationContext(), Action.LEAVE, diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java index 9c7f00701d5..3e376184ac2 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -182,6 +182,14 @@ public class BatteryOptimizeUtils { && getAppOptimizationMode() != BatteryOptimizeUtils.MODE_RESTRICTED; } + String getPackageName() { + return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName; + } + + int getUid() { + return mUid; + } + /** Gets the list of installed applications. */ public static ArraySet getInstalledApplications( Context context, IPackageManager ipm) { @@ -257,10 +265,6 @@ public class BatteryOptimizeUtils { } } - String getPackageName() { - return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName; - } - static int getMode(AppOpsManager appOpsManager, int uid, String packageName) { return appOpsManager.checkOpNoThrow( AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); diff --git a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java index b662d3ef908..2d2c838bc36 100644 --- a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java +++ b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java @@ -35,6 +35,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.HelpUtils; @@ -121,6 +122,10 @@ public class PowerBackgroundUsageDetail extends DashboardFragment mExecutor.execute( () -> { + if (currentOptimizeMode != mOptimizationMode) { + AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid( + getContext(), mBatteryOptimizeUtils.getUid()); + } BatteryOptimizeLogUtils.writeLog( getContext().getApplicationContext(), Action.LEAVE, diff --git a/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt b/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt new file mode 100644 index 00000000000..60db0310040 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2024 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.settings.fuelgauge.batteryusage + +import android.content.Context +import android.content.SharedPreferences +import android.util.ArrayMap +import android.util.Base64 +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action +import com.android.settings.fuelgauge.BatteryOptimizeUtils +import com.android.settings.fuelgauge.BatteryUtils + +/** A util to store and update app optimization mode expiration event data. */ +object AppOptModeSharedPreferencesUtils { + private const val TAG: String = "AppOptModeSharedPreferencesUtils" + private const val SHARED_PREFS_FILE: String = "app_optimization_mode_shared_prefs" + + @VisibleForTesting const val UNLIMITED_EXPIRE_TIME: Long = -1L + + private val appOptimizationModeLock = Any() + private val defaultInstance = AppOptimizationModeEvent.getDefaultInstance() + + /** Returns all app optimization mode events for log. */ + @JvmStatic + fun getAllEvents(context: Context): List = + synchronized(appOptimizationModeLock) { getAppOptModeEventsMap(context).values.toList() } + + /** Updates the app optimization mode event data. */ + @JvmStatic + fun updateAppOptModeExpiration( + context: Context, + uids: List, + packageNames: List, + optimizationModes: List, + expirationTimes: LongArray, + ) = + // The internal fun with an additional lambda parameter is used to + // 1) get true BatteryOptimizeUtils in production environment + // 2) get fake BatteryOptimizeUtils for testing environment + updateAppOptModeExpirationInternal( + context, + uids, + packageNames, + optimizationModes, + expirationTimes + ) { uid: Int, packageName: String -> + BatteryOptimizeUtils(context, uid, packageName) + } + + /** Resets the app optimization mode event data since the query timestamp. */ + @JvmStatic + fun resetExpiredAppOptModeBeforeTimestamp(context: Context, queryTimestamp: Long) = + synchronized(appOptimizationModeLock) { + val eventsMap = getAppOptModeEventsMap(context) + val expirationUids = ArrayList(eventsMap.size) + for ((uid, event) in eventsMap) { + if (event.expirationTime > queryTimestamp) { + continue + } + updateBatteryOptimizationMode( + context, + event.uid, + event.packageName, + event.resetOptimizationMode, + Action.EXPIRATION_RESET, + ) + expirationUids.add(uid) + } + // Remove the expired AppOptimizationModeEvent data from storage + clearSharedPreferences(context, expirationUids) + } + + /** Deletes all app optimization mode event data with a specific uid. */ + @JvmStatic + fun deleteAppOptimizationModeEventByUid(context: Context, uid: Int) = + synchronized(appOptimizationModeLock) { clearSharedPreferences(context, listOf(uid)) } + + @VisibleForTesting + fun updateAppOptModeExpirationInternal( + context: Context, + uids: List, + packageNames: List, + optimizationModes: List, + expirationTimes: LongArray, + getBatteryOptimizeUtils: (Int, String) -> BatteryOptimizeUtils + ) = + synchronized(appOptimizationModeLock) { + val eventsMap = getAppOptModeEventsMap(context) + val expirationEvents: MutableMap = ArrayMap() + for (i in uids.indices) { + val uid = uids[i] + val packageName = packageNames[i] + val optimizationMode = optimizationModes[i] + val originalOptMode: Int = + updateBatteryOptimizationMode( + context, + uid, + packageName, + optimizationMode, + Action.EXTERNAL_UPDATE, + getBatteryOptimizeUtils(uid, packageName) + ) + if (originalOptMode == BatteryOptimizeUtils.MODE_UNKNOWN) { + continue + } + // Make sure the reset mode is consistent with the expiration event in storage. + val resetOptMode = eventsMap[uid]?.resetOptimizationMode ?: originalOptMode + val expireTimeMs: Long = expirationTimes[i] + if (expireTimeMs != UNLIMITED_EXPIRE_TIME) { + Log.d( + TAG, + "setOptimizationMode($packageName) from $originalOptMode " + + "to $optimizationMode with expiration time $expireTimeMs", + ) + expirationEvents[uid] = + AppOptimizationModeEvent.newBuilder() + .setUid(uid) + .setPackageName(packageName) + .setResetOptimizationMode(resetOptMode) + .setExpirationTime(expireTimeMs) + .build() + } + } + + // Append and update the AppOptimizationModeEvent. + if (expirationEvents.isNotEmpty()) { + updateSharedPreferences(context, expirationEvents) + } + } + + @VisibleForTesting + fun updateBatteryOptimizationMode( + context: Context, + uid: Int, + packageName: String, + optimizationMode: Int, + action: Action, + batteryOptimizeUtils: BatteryOptimizeUtils = BatteryOptimizeUtils(context, uid, packageName) + ): Int { + if (!batteryOptimizeUtils.isOptimizeModeMutable) { + Log.w(TAG, "Fail to update immutable optimization mode for: $packageName") + return BatteryOptimizeUtils.MODE_UNKNOWN + } + val currentOptMode = batteryOptimizeUtils.appOptimizationMode + batteryOptimizeUtils.setAppUsageState(optimizationMode, action) + Log.d( + TAG, + "setAppUsageState($packageName) to $optimizationMode with action = ${action.name}", + ) + return currentOptMode + } + + private fun getSharedPreferences(context: Context): SharedPreferences { + return context.applicationContext.getSharedPreferences( + SHARED_PREFS_FILE, + Context.MODE_PRIVATE, + ) + } + + private fun getAppOptModeEventsMap(context: Context): ArrayMap { + val sharedPreferences = getSharedPreferences(context) + val allKeys = sharedPreferences.all?.keys ?: emptySet() + if (allKeys.isEmpty()) { + return ArrayMap() + } + val eventsMap = ArrayMap(allKeys.size) + for (key in allKeys) { + sharedPreferences.getString(key, null)?.let { + eventsMap[key.toInt()] = deserializeAppOptimizationModeEvent(it) + } + } + return eventsMap + } + + private fun updateSharedPreferences( + context: Context, + eventsMap: Map + ) { + val sharedPreferences = getSharedPreferences(context) + sharedPreferences.edit().run { + for ((uid, event) in eventsMap) { + putString(uid.toString(), serializeAppOptimizationModeEvent(event)) + } + apply() + } + } + + private fun clearSharedPreferences(context: Context, uids: List) { + val sharedPreferences = getSharedPreferences(context) + sharedPreferences.edit().run { + for (uid in uids) { + remove(uid.toString()) + } + apply() + } + } + + private fun serializeAppOptimizationModeEvent(event: AppOptimizationModeEvent): String { + return Base64.encodeToString(event.toByteArray(), Base64.DEFAULT) + } + + private fun deserializeAppOptimizationModeEvent( + encodedProtoString: String + ): AppOptimizationModeEvent { + return BatteryUtils.parseProtoFromString(encodedProtoString, defaultInstance) + } +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java index 26bb6dd0a01..08369127b6d 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java @@ -167,6 +167,8 @@ public final class BatteryUsageDataLoader { try { final long start = System.currentTimeMillis(); loadBatteryStatsData(context, isFullChargeStart); + AppOptModeSharedPreferencesUtils.resetExpiredAppOptModeBeforeTimestamp( + context, System.currentTimeMillis()); if (!isFullChargeStart) { // No app usage data or battery diff data at this time. final UserIdsSeries userIdsSeries = diff --git a/src/com/android/settings/fuelgauge/protos/Android.bp b/src/com/android/settings/fuelgauge/protos/Android.bp index 462962b63c5..40fb987e62c 100644 --- a/src/com/android/settings/fuelgauge/protos/Android.bp +++ b/src/com/android/settings/fuelgauge/protos/Android.bp @@ -9,41 +9,9 @@ package { } java_library { - name: "app-usage-event-protos-lite", + name: "fuelgauge-protos-lite", proto: { type: "lite", }, - srcs: ["app_usage_event.proto"], -} - -java_library { - name: "battery-event-protos-lite", - proto: { - type: "lite", - }, - srcs: ["battery_event.proto"], -} - -java_library { - name: "battery-usage-slot-protos-lite", - proto: { - type: "lite", - }, - srcs: ["battery_usage_slot.proto"], -} - -java_library { - name: "fuelgauge-usage-state-protos-lite", - proto: { - type: "lite", - }, - srcs: ["fuelgauge_usage_state.proto"], -} - -java_library { - name: "power-anomaly-event-protos-lite", - proto: { - type: "lite", - }, - srcs: ["power_anomaly_event.proto"], + srcs: ["*.proto"], } diff --git a/src/com/android/settings/fuelgauge/protos/app_optimization_mode_event.proto b/src/com/android/settings/fuelgauge/protos/app_optimization_mode_event.proto new file mode 100644 index 00000000000..81d51bc0d6b --- /dev/null +++ b/src/com/android/settings/fuelgauge/protos/app_optimization_mode_event.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "com.android.settings.fuelgauge.batteryusage"; +option java_outer_classname = "AppOptimizationModeEventProto"; + +message AppOptimizationModeEvents { + // Map of uid to AppOptimizationModeEvent + map events = 1; +} + +message AppOptimizationModeEvent { + optional int32 uid = 1; + optional string package_name = 2; + // Value of BatteryUsageSlot.BatteryOptimizationMode, range = [0,3] + optional int32 reset_optimization_mode = 3; + optional int64 expiration_time = 4; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt new file mode 100644 index 00000000000..02d3739a9c1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 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.settings.fuelgauge.batteryusage + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action +import com.android.settings.fuelgauge.BatteryOptimizeUtils +import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_OPTIMIZED +import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED +import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNKNOWN +import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED +import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils.UNLIMITED_EXPIRE_TIME +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class AppOptModeSharedPreferencesUtilsTest { + @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Spy private var context: Context = ApplicationProvider.getApplicationContext() + + @Spy + private var testBatteryOptimizeUtils = spy(BatteryOptimizeUtils(context, UID, PACKAGE_NAME)) + + @Before + fun setup() { + AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(context, UID) + } + + @Test + fun getAllEvents_emptyData_verifyEmptyList() { + assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty() + } + + @Test + fun updateAppOptModeExpirationInternal_withExpirationTime_verifyData() { + insertAppOptModeEventForTest(/* expirationTime= */ 1000L) + + val events = AppOptModeSharedPreferencesUtils.getAllEvents(context) + + assertThat(events.size).isEqualTo(1) + assertAppOptimizationModeEventInfo(events.get(0), UID, PACKAGE_NAME, MODE_OPTIMIZED, 1000L) + } + + @Test + fun updateAppOptModeExpirationInternal_withoutExpirationTime_verifyEmptyList() { + insertAppOptModeEventForTest(/* expirationTime= */ UNLIMITED_EXPIRE_TIME) + + assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty() + } + + @Test + fun deleteAppOptimizationModeEventByUid_uidNotContained_verifyData() { + insertAppOptModeEventForTest(/* expirationTime= */ 1000L) + assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context).size).isEqualTo(1) + + AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(context, UNSET_UID) + val events = AppOptModeSharedPreferencesUtils.getAllEvents(context) + + assertThat(events.size).isEqualTo(1) + assertAppOptimizationModeEventInfo(events.get(0), UID, PACKAGE_NAME, MODE_OPTIMIZED, 1000L) + } + + @Test + fun deleteAppOptimizationModeEventByUid_uidExisting_verifyData() { + insertAppOptModeEventForTest(/* expirationTime= */ 1000L) + + AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(context, UID) + + assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty() + } + + @Test + fun resetExpiredAppOptModeBeforeTimestamp_noExpiredData_verifyData() { + insertAppOptModeEventForTest(/* expirationTime= */ 1000L) + + AppOptModeSharedPreferencesUtils.resetExpiredAppOptModeBeforeTimestamp(context, 999L) + val events = AppOptModeSharedPreferencesUtils.getAllEvents(context) + + assertThat(events.size).isEqualTo(1) + assertAppOptimizationModeEventInfo(events.get(0), UID, PACKAGE_NAME, MODE_OPTIMIZED, 1000L) + } + + @Test + fun resetExpiredAppOptModeBeforeTimestamp_hasExpiredData_verifyEmptyList() { + insertAppOptModeEventForTest(/* expirationTime= */ 1000L) + + AppOptModeSharedPreferencesUtils.resetExpiredAppOptModeBeforeTimestamp(context, 1001L) + + assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty() + } + + @Test + fun updateBatteryOptimizationMode_updateToOptimizedMode_verifyAction() { + whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(true) + whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)) + .thenReturn(MODE_UNRESTRICTED) + + val currentOptMode = + AppOptModeSharedPreferencesUtils.updateBatteryOptimizationMode( + context, + UID, + PACKAGE_NAME, + MODE_OPTIMIZED, + Action.EXTERNAL_UPDATE, + testBatteryOptimizeUtils + ) + + verify(testBatteryOptimizeUtils)?.setAppUsageState(MODE_OPTIMIZED, Action.EXTERNAL_UPDATE) + assertThat(currentOptMode).isEqualTo(MODE_UNRESTRICTED) + } + + @Test + fun updateBatteryOptimizationMode_optimizationModeNotChanged_verifyAction() { + whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(false) + whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)) + .thenReturn(MODE_UNRESTRICTED) + + val currentOptMode = + AppOptModeSharedPreferencesUtils.updateBatteryOptimizationMode( + context, + UID, + PACKAGE_NAME, + MODE_OPTIMIZED, + Action.EXTERNAL_UPDATE, + testBatteryOptimizeUtils + ) + + verify(testBatteryOptimizeUtils, never())?.setAppUsageState(anyInt(), any()) + assertThat(currentOptMode).isEqualTo(MODE_UNKNOWN) + } + + @Test + fun updateBatteryOptimizationMode_updateToSameOptimizationMode_verifyAction() { + whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(true) + whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)).thenReturn(MODE_RESTRICTED) + + val currentOptMode = + AppOptModeSharedPreferencesUtils.updateBatteryOptimizationMode( + context, + UID, + PACKAGE_NAME, + MODE_RESTRICTED, + Action.EXTERNAL_UPDATE, + testBatteryOptimizeUtils + ) + + verify(testBatteryOptimizeUtils)?.setAppUsageState(MODE_RESTRICTED, Action.EXTERNAL_UPDATE) + assertThat(currentOptMode).isEqualTo(MODE_RESTRICTED) + } + + private fun insertAppOptModeEventForTest(expirationTime: Long) { + whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(true) + whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)).thenReturn(MODE_OPTIMIZED) + AppOptModeSharedPreferencesUtils.updateAppOptModeExpirationInternal( + context, + mutableListOf(UID), + mutableListOf(PACKAGE_NAME), + mutableListOf(MODE_OPTIMIZED), + longArrayOf(expirationTime) + ) { _: Int, _: String -> + testBatteryOptimizeUtils + } + } + + companion object { + const val UID: Int = 12345 + const val UNSET_UID: Int = 15432 + const val PACKAGE_NAME: String = "com.android.app" + + private fun assertAppOptimizationModeEventInfo( + event: AppOptimizationModeEvent, + uid: Int, + packageName: String, + resetOptimizationMode: Int, + expirationTime: Long + ) { + assertThat(event.uid).isEqualTo(uid) + assertThat(event.packageName).isEqualTo(packageName) + assertThat(event.resetOptimizationMode).isEqualTo(resetOptimizationMode) + assertThat(event.expirationTime).isEqualTo(expirationTime) + } + } +} From ffba0e7a404bb19daa9ed480b3361dd71593687b Mon Sep 17 00:00:00 2001 From: yomna Date: Fri, 31 May 2024 19:12:05 +0000 Subject: [PATCH 5/6] Control visibility of Cellular Security tile on More Security & Privacy page Bug: b/335554085 Test: manual Flag: EXEMPT bugfix Change-Id: I35a3b67abc8339179b600fa83effd98e5b389a37 --- res/xml/more_security_privacy_settings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml index f1678bc2f3f..799ff1e815a 100644 --- a/res/xml/more_security_privacy_settings.xml +++ b/res/xml/more_security_privacy_settings.xml @@ -121,6 +121,7 @@ android:title="@string/cellular_security_title" android:summary="@string/cellular_security_summary" android:fragment="com.android.settings.network.telephony.CellularSecuritySettingsFragment" + settings:controller="com.android.settings.network.CellularSecurityPreferenceController" settings:searchable="false"/> From 15930b1b61608c5250b29f805c683948e7087a7e Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 3 Jun 2024 14:11:19 -0400 Subject: [PATCH 6/6] Reenable (no longer) failing test Test: ApprovalPreferenceControllerTest Fixes: 339550695 Flag: android.app.modes_api Change-Id: Id71a17edd3fa1e9a03df3947c159161eee4dcd1a --- .../notificationaccess/ApprovalPreferenceControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java index ac86eb071f2..e488792d55b 100644 --- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java @@ -189,8 +189,8 @@ public class ApprovalPreferenceControllerTest { @Test @EnableFlags(Flags.FLAG_MODES_API) - @Ignore("b/339550695") public void disable() { + when(mNm.isNotificationPolicyAccessGrantedForPackage(anyString())).thenReturn(false); mController.disable(mCn); verify(mFeatureFactory.metricsFeatureProvider).action( mContext,