From 035b661ea4920d82caf47f753ee668be0f01bd83 Mon Sep 17 00:00:00 2001 From: Austin Delgado Date: Mon, 31 Jul 2023 17:49:15 -0700 Subject: [PATCH 1/4] Announce requirements when setting PIN/Pass Test: Manual, verify requirement is read Fixes: 281955512 Change-Id: I6d963d545c68c4ec891d9ad8a7da8c0a0f6e9d6b --- src/com/android/settings/password/ChooseLockPassword.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 16c4f5bb1ab..a72bff47ab0 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -520,6 +520,7 @@ public class ChooseLockPassword extends SettingsActivity { setupPasswordRequirementsView(view); mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); + mPasswordRestrictionView.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); mPasswordEntry = view.findViewById(R.id.password_entry); mPasswordEntry.setOnEditorActionListener(this); mPasswordEntry.addTextChangedListener(this); From 64177774e279e2e9727b9284844542ba38c4ef6f Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Fri, 28 Jul 2023 13:31:31 +0800 Subject: [PATCH 2/4] Add cache strategy for getUsageSource(). Cache usage source into SharedPreferences when phone is booting to avoid calling it too frequently. It should be safe because the usage source can only change on reboot. Bug: 293366011 Test: make RunSettingsRoboTests Change-Id: I35c07539d294737c5764b03b746cfb39f4ce008d --- .../batteryusage/BootBroadcastReceiver.java | 4 + .../fuelgauge/batteryusage/ConvertUtils.java | 33 ++++----- .../batteryusage/DataProcessManager.java | 4 +- .../fuelgauge/batteryusage/DataProcessor.java | 23 ++---- .../fuelgauge/batteryusage/DatabaseUtils.java | 45 ++++++++++- .../BootBroadcastReceiverTest.java | 30 +++++--- .../batteryusage/ConvertUtilsTest.java | 47 +++++------- .../batteryusage/DataProcessManagerTest.java | 2 +- .../batteryusage/DataProcessorTest.java | 13 ++-- .../batteryusage/DatabaseUtilsTest.java | 74 +++++++++++++++++++ 10 files changed, 190 insertions(+), 85 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java index 920670fa13f..6d14e1c72e4 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java @@ -83,6 +83,10 @@ public final class BootBroadcastReceiver extends BroadcastReceiver { recheckIntent.setClass(context, BootBroadcastReceiver.class); final long delayedTime = getRescheduleTimeForBootAction(context); mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), delayedTime); + + // Refreshes the usage source from UsageStatsManager when booting. + DatabaseUtils.removeUsageSource(context); + BatteryUsageLogUtils.writeLog(context, Action.RECHECK_JOB, "delay:" + delayedTime); } else if (ACTION_SETUP_WIZARD_FINISHED.equals(action)) { ElapsedTimeUtils.storeSuwFinishedTimestamp(context, System.currentTimeMillis()); diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java index 2c98c4bd249..3fc44ccfd9b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java @@ -17,7 +17,6 @@ package com.android.settings.fuelgauge.batteryusage; import android.annotation.IntDef; import android.annotation.Nullable; -import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManager; import android.content.ContentValues; @@ -27,7 +26,6 @@ import android.database.Cursor; import android.os.BatteryUsageStats; import android.os.Build; import android.os.LocaleList; -import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.text.format.DateFormat; @@ -67,6 +65,12 @@ public final class ConvertUtils { public static final int CONSUMER_TYPE_USER_BATTERY = 2; public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3; + public static final int DEFAULT_USAGE_SOURCE = UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY; + public static final int EMPTY_USAGE_SOURCE = -1; + + @VisibleForTesting + static int sUsageSource = EMPTY_USAGE_SOURCE; + private ConvertUtils() { } @@ -181,8 +185,7 @@ public final class ConvertUtils { /** Converts to {@link AppUsageEvent} from {@link Event} */ @Nullable public static AppUsageEvent convertToAppUsageEvent( - Context context, final IUsageStatsManager usageStatsManager, final Event event, - final long userId) { + Context context, final Event event, final long userId) { final String packageName = event.getPackageName(); if (packageName == null) { // See b/190609174: Event package names should never be null, but sometimes they are. @@ -207,7 +210,7 @@ public final class ConvertUtils { } final String effectivePackageName = - getEffectivePackageName(usageStatsManager, packageName, taskRootPackageName); + getEffectivePackageName(context, packageName, taskRootPackageName); try { final long uid = context .getPackageManager() @@ -323,9 +326,8 @@ public final class ConvertUtils { */ @VisibleForTesting static String getEffectivePackageName( - final IUsageStatsManager usageStatsManager, final String packageName, - final String taskRootPackageName) { - int usageSource = getUsageSource(usageStatsManager); + Context context, final String packageName, final String taskRootPackageName) { + final int usageSource = getUsageSource(context); switch (usageSource) { case UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY: return !TextUtils.isEmpty(taskRootPackageName) @@ -370,18 +372,11 @@ public final class ConvertUtils { } } - /** - * Returns what App Usage Observers will consider the source of usage for an activity. - * - * @see UsageStatsManager#getUsageSource() - */ - private static int getUsageSource(final IUsageStatsManager usageStatsManager) { - try { - return usageStatsManager.getUsageSource(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to getUsageSource", e); - return UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY; + private static int getUsageSource(Context context) { + if (sUsageSource == EMPTY_USAGE_SOURCE) { + sUsageSource = DatabaseUtils.getUsageSource(context); } + return sUsageSource; } private static AppUsageEventType getAppUsageEventType(final int eventType) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java index 0f67e6a7a5a..1c851fd4ca9 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java @@ -397,8 +397,8 @@ public class DataProcessManager { } // Generates the indexed AppUsagePeriod list data for each corresponding time slot for // further use. - mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(mRawStartTimestamp, - mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList); + mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap( + mContext, mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList); } private void tryToGenerateFinalDataAndApplyCallback() { diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index 6914c3043ea..badc35935fa 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -32,7 +32,6 @@ import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; import android.os.UserHandle; @@ -78,8 +77,6 @@ public final class DataProcessor { private static final int MIN_DAILY_DATA_SIZE = 2; private static final int MIN_TIMESTAMP_DATA_SIZE = 2; private static final int MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP = 5; - // Maximum total time value for each hourly slot cumulative data at most 2 hours. - private static final float TOTAL_HOURLY_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2; private static final long MIN_TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2; private static final String MEDIASERVER_PACKAGE_NAME = "mediaserver"; private static final String ANDROID_CORE_APPS_SHARED_USER_ID = "android.uid.shared"; @@ -111,11 +108,6 @@ public final class DataProcessor { @VisibleForTesting static Set sTestSystemAppsPackageNames; - @VisibleForTesting - static IUsageStatsManager sUsageStatsManager = - IUsageStatsManager.Stub.asInterface( - ServiceManager.getService(Context.USAGE_STATS_SERVICE)); - public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER = "CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER"; @@ -271,7 +263,7 @@ public final class DataProcessor { @Nullable public static Map>>>> generateAppUsagePeriodMap( - final long rawStartTimestamp, + Context context, final List hourlyBatteryLevelsPerDay, final List appUsageEventList, final List batteryEventList) { @@ -305,7 +297,7 @@ public final class DataProcessor { // The value could be null when there is no data in the hourly slot. dailyMap.put( hourlyIndex, - buildAppUsagePeriodList(hourlyAppUsageEventList, batteryEventList, + buildAppUsagePeriodList(context, hourlyAppUsageEventList, batteryEventList, startTimestamp, endTimestamp)); } } @@ -346,8 +338,7 @@ public final class DataProcessor { break; } final AppUsageEvent appUsageEvent = - ConvertUtils.convertToAppUsageEvent( - context, sUsageStatsManager, event, userId); + ConvertUtils.convertToAppUsageEvent(context, event, userId); if (appUsageEvent != null) { numEventsFetched++; appUsageEventList.add(appUsageEvent); @@ -661,8 +652,8 @@ public final class DataProcessor { @VisibleForTesting @Nullable static Map>> buildAppUsagePeriodList( - final List appUsageEvents, final List batteryEventList, - final long startTime, final long endTime) { + Context context, final List appUsageEvents, + final List batteryEventList, final long startTime, final long endTime) { if (appUsageEvents.isEmpty()) { return null; } @@ -702,7 +693,7 @@ public final class DataProcessor { final AppUsageEvent firstEvent = usageEvents.get(0); final long eventUserId = firstEvent.getUserId(); final String packageName = getEffectivePackageName( - sUsageStatsManager, + context, firstEvent.getPackageName(), firstEvent.getTaskRootPackageName()); usageEvents.addAll(deviceEvents); @@ -975,7 +966,7 @@ public final class DataProcessor { final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser( context, userID, earliestTimestamp); return loadAppUsageEventsForUserFromService( - sUsageStatsManager, startTime, now, userID, callingPackage); + DatabaseUtils.sUsageStatsManager, startTime, now, userID, callingPackage); } @Nullable diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java index 8d1a2f90991..ea1f3ed9e79 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java @@ -15,6 +15,8 @@ */ package com.android.settings.fuelgauge.batteryusage; +import android.app.usage.IUsageStatsManager; +import android.app.usage.UsageStatsManager; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -28,6 +30,8 @@ import android.os.BatteryManager; import android.os.BatteryUsageStats; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserManager; import android.util.Log; @@ -63,6 +67,7 @@ public final class DatabaseUtils { static final int DATA_RETENTION_INTERVAL_DAY = 9; static final String KEY_LAST_LOAD_FULL_CHARGE_TIME = "last_load_full_charge_time"; static final String KEY_LAST_UPLOAD_FULL_CHARGE_TIME = "last_upload_full_charge_time"; + static final String KEY_LAST_USAGE_SOURCE = "last_usage_source"; /** An authority name of the battery content provider. */ public static final String AUTHORITY = "com.android.settings.battery.usage.provider"; @@ -74,8 +79,6 @@ public final class DatabaseUtils { public static final String BATTERY_STATE_TABLE = "BatteryState"; /** A path name for app usage latest timestamp query. */ public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp"; - /** A class name for battery usage data provider. */ - public static final String SETTINGS_PACKAGE_PATH = "com.android.settings"; /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/ public static final String QUERY_KEY_TIMESTAMP = "timestamp"; /** Key for query parameter userid used in APP_USAGE_EVENT_URI **/ @@ -114,6 +117,11 @@ public final class DatabaseUtils { @VisibleForTesting static Supplier sFakeSupplier; + @VisibleForTesting + static IUsageStatsManager sUsageStatsManager = + IUsageStatsManager.Stub.asInterface( + ServiceManager.getService(Context.USAGE_STATS_SERVICE)); + private DatabaseUtils() { } @@ -468,6 +476,37 @@ public final class DatabaseUtils { SHARED_PREFS_FILE, Context.MODE_PRIVATE); } + static void removeUsageSource(Context context) { + final SharedPreferences sharedPreferences = getSharedPreferences(context); + if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) { + sharedPreferences.edit().remove(KEY_LAST_USAGE_SOURCE).apply(); + } + } + + /** + * Returns what App Usage Observers will consider the source of usage for an activity. + * + * @see UsageStatsManager#getUsageSource() + */ + static int getUsageSource(Context context) { + final SharedPreferences sharedPreferences = getSharedPreferences(context); + if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) { + return sharedPreferences + .getInt(KEY_LAST_USAGE_SOURCE, ConvertUtils.DEFAULT_USAGE_SOURCE); + } + int usageSource = ConvertUtils.DEFAULT_USAGE_SOURCE; + + try { + usageSource = sUsageStatsManager.getUsageSource(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to getUsageSource", e); + } + if (sharedPreferences != null) { + sharedPreferences.edit().putInt(KEY_LAST_USAGE_SOURCE, usageSource).apply(); + } + return usageSource; + } + static void recordDateTime(Context context, String preferenceKey) { final SharedPreferences sharedPreferences = getSharedPreferences(context); if (sharedPreferences != null) { @@ -564,7 +603,7 @@ public final class DatabaseUtils { private static Map> loadHistoryMapFromContentProvider( Context context, Uri batteryStateUri) { - context = DatabaseUtils.getParentContext(context); + context = getParentContext(context); if (context == null) { return null; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java index aa1ebd7b44c..566df5251d5 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java @@ -22,8 +22,10 @@ import static org.robolectric.Shadows.shadowOf; import android.app.AlarmManager; import android.app.Application; +import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import androidx.test.core.app.ApplicationProvider; @@ -40,7 +42,6 @@ import org.robolectric.Shadows; import org.robolectric.shadows.ShadowAlarmManager; import java.time.Clock; -import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -65,10 +66,12 @@ public final class BootBroadcastReceiverTest { BatteryTestUtils.insertDataToBatteryStateTable( mContext, Clock.systemUTC().millis(), "com.android.systemui"); mDao = database.batteryStateDao(); + clearSharedPreferences(); } @After public void tearDown() { + clearSharedPreferences(); mPeriodicJobManager.reset(); } @@ -82,8 +85,21 @@ public final class BootBroadcastReceiverTest { @Test public void onReceive_withBootCompletedIntent_refreshesJob() { + final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext); + sharedPreferences + .edit() + .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, + UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY) + .apply(); + mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED)); + assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull(); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE)) + .isFalse(); } @Test @@ -133,15 +149,7 @@ public final class BootBroadcastReceiverTest { BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK); } - private void insertExpiredData(int shiftDay) { - final long expiredTimeInMs = - Clock.systemUTC().millis() - Duration.ofDays(shiftDay).toMillis(); - BatteryTestUtils.insertDataToBatteryStateTable( - mContext, expiredTimeInMs - 1, "com.android.systemui"); - BatteryTestUtils.insertDataToBatteryStateTable( - mContext, expiredTimeInMs, "com.android.systemui"); - // Ensures the testing environment is correct. - assertThat(mDao.getAllAfter(0)).hasSize(3); + private void clearSharedPreferences() { + DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply(); } - } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java index 6b8073b68ee..3cbe8a44d1f 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java @@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageEvents.Event; import android.content.ContentValues; @@ -35,7 +34,6 @@ import android.database.MatrixCursor; import android.os.BatteryManager; import android.os.BatteryUsageStats; import android.os.LocaleList; -import android.os.RemoteException; import android.os.UserHandle; import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; @@ -62,14 +60,13 @@ public final class ConvertUtilsTest { @Mock private BatteryUsageStats mBatteryUsageStats; @Mock - private IUsageStatsManager mUsageStatsManager; - @Mock private BatteryEntry mMockBatteryEntry; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + ConvertUtils.sUsageSource = ConvertUtils.EMPTY_USAGE_SOURCE; when(mContext.getPackageManager()).thenReturn(mMockPackageManager); } @@ -302,7 +299,7 @@ public final class ConvertUtilsTest { final long userId = 2; final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, userId); + mContext, event, userId); assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L); assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.ACTIVITY_RESUMED); assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1"); @@ -322,8 +319,8 @@ public final class ConvertUtilsTest { when(mMockPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(1001); final long userId = 1; - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, userId); + final AppUsageEvent appUsageEvent = + ConvertUtils.convertToAppUsageEvent(mContext, event, userId); assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L); assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.DEVICE_SHUTDOWN); assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1"); @@ -338,8 +335,8 @@ public final class ConvertUtilsTest { final Event event = new Event(); event.mPackage = null; - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, /*userId=*/ 0); + final AppUsageEvent appUsageEvent = + ConvertUtils.convertToAppUsageEvent(mContext, event, /*userId=*/ 0); assertThat(appUsageEvent).isNull(); } @@ -354,8 +351,8 @@ public final class ConvertUtilsTest { .thenThrow(new PackageManager.NameNotFoundException()); final long userId = 1; - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent( - mContext, mUsageStatsManager, event, userId); + final AppUsageEvent appUsageEvent = + ConvertUtils.convertToAppUsageEvent(mContext, event, userId); assertThat(appUsageEvent).isNull(); } @@ -450,51 +447,47 @@ public final class ConvertUtilsTest { } @Test - public void getEffectivePackageName_currentActivity_returnPackageName() throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_CURRENT_ACTIVITY); + public void getEffectivePackageName_currentActivity_returnPackageName() { + ConvertUtils.sUsageSource = USAGE_SOURCE_CURRENT_ACTIVITY; final String packageName = "com.android.settings1"; final String taskRootPackageName = "com.android.settings2"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, taskRootPackageName)) + mContext, packageName, taskRootPackageName)) .isEqualTo(packageName); } @Test - public void getEffectivePackageName_usageSourceThrowException_returnPackageName() - throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException()); + public void getEffectivePackageName_emptyUsageSource_returnPackageName() { final String packageName = "com.android.settings1"; final String taskRootPackageName = "com.android.settings2"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, taskRootPackageName)) + mContext, packageName, taskRootPackageName)) .isEqualTo(packageName); } @Test - public void getEffectivePackageName_rootActivity_returnTaskRootPackageName() - throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + public void getEffectivePackageName_rootActivity_returnTaskRootPackageName() { + ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY; final String packageName = "com.android.settings1"; final String taskRootPackageName = "com.android.settings2"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, taskRootPackageName)) + mContext, packageName, taskRootPackageName)) .isEqualTo(taskRootPackageName); } @Test - public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName() - throws RemoteException { - when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName() { + ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY; final String packageName = "com.android.settings1"; assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, /*taskRootPackageName=*/ null)) + mContext, packageName, /*taskRootPackageName=*/ null)) .isEqualTo(packageName); assertThat(ConvertUtils.getEffectivePackageName( - mUsageStatsManager, packageName, /*taskRootPackageName=*/ "")) + mContext, packageName, /*taskRootPackageName=*/ "")) .isEqualTo(packageName); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java index b610cfbf289..7f7fe43bb9e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java @@ -72,7 +72,7 @@ public final class DataProcessManagerTest { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - DataProcessor.sUsageStatsManager = mUsageStatsManager; + DatabaseUtils.sUsageStatsManager = mUsageStatsManager; doReturn(mContext).when(mContext).getApplicationContext(); doReturn(mUserManager) .when(mContext) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index e2274e2d5e1..8bed054e91e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -93,7 +93,7 @@ public final class DataProcessorTest { mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider; DataProcessor.sTestSystemAppsPackageNames = Set.of(); - DataProcessor.sUsageStatsManager = mUsageStatsManager; + DatabaseUtils.sUsageStatsManager = mUsageStatsManager; doReturn(mIntent).when(mContext).registerReceiver( isA(BroadcastReceiver.class), isA(IntentFilter.class)); doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt()); @@ -249,7 +249,7 @@ public final class DataProcessorTest { final Map>>>> periodMap = DataProcessor.generateAppUsagePeriodMap( - 14400000L, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>()); + mContext, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>()); assertThat(periodMap).hasSize(3); // Day 1 @@ -287,7 +287,8 @@ public final class DataProcessorTest { hourlyBatteryLevelsPerDay.add( new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>())); assertThat(DataProcessor.generateAppUsagePeriodMap( - 0L, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())).isNull(); + mContext, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())) + .isNull(); } @Test @@ -1644,7 +1645,7 @@ public final class DataProcessorTest { final Map>> appUsagePeriodMap = DataProcessor.buildAppUsagePeriodList( - appUsageEvents, new ArrayList<>(), 0, 5); + mContext, appUsageEvents, new ArrayList<>(), 0, 5); assertThat(appUsagePeriodMap).hasSize(2); final Map> userMap1 = appUsagePeriodMap.get(1L); @@ -1668,7 +1669,7 @@ public final class DataProcessorTest { @Test public void buildAppUsagePeriodList_emptyEventList_returnNull() { assertThat(DataProcessor.buildAppUsagePeriodList( - new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull(); + mContext, new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull(); } @Test @@ -1680,7 +1681,7 @@ public final class DataProcessorTest { AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 2)); assertThat(DataProcessor.buildAppUsagePeriodList( - appUsageEvents, new ArrayList<>(), 0, 3)).isNull(); + mContext, appUsageEvents, new ArrayList<>(), 0, 3)).isNull(); } @Test diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java index efce44e600f..24be7698375 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java @@ -16,6 +16,9 @@ package com.android.settings.fuelgauge.batteryusage; +import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY; +import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -23,15 +26,19 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import android.app.usage.IUsageStatsManager; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.MatrixCursor; import android.os.BatteryManager; import android.os.BatteryUsageStats; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -67,6 +74,7 @@ public final class DatabaseUtilsTest { @Mock private BatteryEntry mMockBatteryEntry2; @Mock private BatteryEntry mMockBatteryEntry3; @Mock private Context mMockContext; + @Mock private IUsageStatsManager mUsageStatsManager; @Before public void setUp() { @@ -77,6 +85,7 @@ public final class DatabaseUtilsTest { doReturn(mPackageManager).when(mMockContext).getPackageManager(); doReturn(mPackageManager).when(mContext).getPackageManager(); DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply(); + DatabaseUtils.sUsageStatsManager = mUsageStatsManager; } @Test @@ -422,6 +431,71 @@ public final class DatabaseUtilsTest { assertThat(batteryHistMap).isEmpty(); } + @Test + public void removeUsageSource_hasNoData() { + DatabaseUtils.removeUsageSource(mContext); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE)) + .isFalse(); + } + + @Test + public void removeUsageSource_hasData_deleteUsageSource() { + final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext); + sharedPreferences + .edit() + .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY) + .apply(); + + DatabaseUtils.removeUsageSource(mContext); + + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE)) + .isFalse(); + } + + @Test + public void getUsageSource_hasData() { + final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext); + sharedPreferences + .edit() + .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY) + .apply(); + + assertThat(DatabaseUtils.getUsageSource(mContext)) + .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + } + + @Test + public void getUsageSource_notHasData_writeLoadedData() throws RemoteException { + when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + + assertThat(DatabaseUtils.getUsageSource(mContext)) + .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY)) + .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY); + } + + @Test + public void getUsageSource_throwException_writeDefaultData() throws RemoteException { + when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException()); + + assertThat(DatabaseUtils.getUsageSource(mContext)) + .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY); + assertThat( + DatabaseUtils + .getSharedPreferences(mContext) + .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY)) + .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY); + } + @Test public void recordDateTime_writeDataIntoSharedPreferences() { final String preferenceKey = "test_preference_key"; From be8407c566e77435e1874f985ca527b32745558c Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Wed, 19 Jul 2023 13:28:52 +0800 Subject: [PATCH 3/4] Implement the battery tips cards.(1/2) - Implement a tips card UI in Battery Usage. [Screenshot]: - Tips Card with thumbs-up/down feedback in dark mode https://screenshot.googleplex.com/3nRCFYvLTWfiYYT - Tips Card without feedback https://screenshot.googleplex.com/B7QGRJZAHzgWpCP - Tips Card in Force RTL layout https://screenshot.googleplex.com/8crQdj8ao26pKpH - Tips Card in light mode https://screenshot.googleplex.com/885aVvZm8xmhK2S [TODO]: - Add accessibility - Localization Bug: 291689623 Test: Manual Change-Id: I4443cdb21b3ba30900fc2f6fcc21c4c56dc1293f Merged-In: I4443cdb21b3ba30900fc2f6fcc21c4c56dc1293f --- res/drawable/battery_tips_all_rounded_bg.xml | 22 +++ .../battery_tips_half_rounded_bottom_bg.xml | 27 ++++ .../battery_tips_half_rounded_top_bg.xml | 27 ++++ res/drawable/ic_battery_tips_close.xml | 25 ++++ res/drawable/ic_battery_tips_close_icon.xml | 32 +++++ res/drawable/ic_battery_tips_lightbulb.xml | 25 ++++ res/drawable/ic_battery_tips_thumb_down.xml | 25 ++++ res/drawable/ic_battery_tips_thumb_up.xml | 25 ++++ res/layout/battery_tips_card.xml | 125 ++++++++++++++++++ res/values/dimens.xml | 4 + res/values/strings.xml | 18 +++ res/xml/power_usage_advanced.xml | 12 ++ .../fuelgauge/PowerUsageFeatureProvider.java | 10 ++ .../PowerUsageFeatureProviderImpl.java | 10 ++ .../BatteryChartPreferenceController.java | 23 ++++ .../BatteryTipsCardPreference.java | 99 ++++++++++++++ .../batteryusage/BatteryTipsController.java | 77 +++++++++++ .../batteryusage/PowerUsageAdvanced.java | 12 ++ .../PowerUsageFeatureProviderImplTest.java | 9 ++ .../BatteryTipsCardPreferenceTest.java | 50 +++++++ 20 files changed, 657 insertions(+) create mode 100644 res/drawable/battery_tips_all_rounded_bg.xml create mode 100644 res/drawable/battery_tips_half_rounded_bottom_bg.xml create mode 100644 res/drawable/battery_tips_half_rounded_top_bg.xml create mode 100644 res/drawable/ic_battery_tips_close.xml create mode 100644 res/drawable/ic_battery_tips_close_icon.xml create mode 100644 res/drawable/ic_battery_tips_lightbulb.xml create mode 100644 res/drawable/ic_battery_tips_thumb_down.xml create mode 100644 res/drawable/ic_battery_tips_thumb_up.xml create mode 100644 res/layout/battery_tips_card.xml create mode 100644 src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java create mode 100644 src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java diff --git a/res/drawable/battery_tips_all_rounded_bg.xml b/res/drawable/battery_tips_all_rounded_bg.xml new file mode 100644 index 00000000000..4f61f544074 --- /dev/null +++ b/res/drawable/battery_tips_all_rounded_bg.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/battery_tips_half_rounded_bottom_bg.xml b/res/drawable/battery_tips_half_rounded_bottom_bg.xml new file mode 100644 index 00000000000..7766de63400 --- /dev/null +++ b/res/drawable/battery_tips_half_rounded_bottom_bg.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/battery_tips_half_rounded_top_bg.xml b/res/drawable/battery_tips_half_rounded_top_bg.xml new file mode 100644 index 00000000000..aba1a4fb329 --- /dev/null +++ b/res/drawable/battery_tips_half_rounded_top_bg.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_tips_close.xml b/res/drawable/ic_battery_tips_close.xml new file mode 100644 index 00000000000..7ef571b52f9 --- /dev/null +++ b/res/drawable/ic_battery_tips_close.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_battery_tips_close_icon.xml b/res/drawable/ic_battery_tips_close_icon.xml new file mode 100644 index 00000000000..b7664740450 --- /dev/null +++ b/res/drawable/ic_battery_tips_close_icon.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_tips_lightbulb.xml b/res/drawable/ic_battery_tips_lightbulb.xml new file mode 100644 index 00000000000..f1449f9bb5b --- /dev/null +++ b/res/drawable/ic_battery_tips_lightbulb.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_tips_thumb_down.xml b/res/drawable/ic_battery_tips_thumb_down.xml new file mode 100644 index 00000000000..cd7656b220f --- /dev/null +++ b/res/drawable/ic_battery_tips_thumb_down.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_tips_thumb_up.xml b/res/drawable/ic_battery_tips_thumb_up.xml new file mode 100644 index 00000000000..b1d4cb66c70 --- /dev/null +++ b/res/drawable/ic_battery_tips_thumb_up.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/layout/battery_tips_card.xml b/res/layout/battery_tips_card.xml new file mode 100644 index 00000000000..d2edb51c0ab --- /dev/null +++ b/res/layout/battery_tips_card.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index a7a124a32c5..e272c15109b 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -373,6 +373,10 @@ 1dp 2dp + + 4dp + 24dp + 174dp 28dp diff --git a/res/values/strings.xml b/res/values/strings.xml index ba5f72db554..8e45f9cd09d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9650,6 +9650,24 @@ Set battery usage for apps + + Optimize + + + Is this message helpful? + + + Turn on adaptive brightness to extend battery life + + + It will help reduce your daily battery drain by 10% + + + Reduce screen timeout to extend battery life + + + It will help reduce your daily battery drain by 10% + Unrestricted diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml index 2a1a23c78fe..c1294535a74 100644 --- a/res/xml/power_usage_advanced.xml +++ b/res/xml/power_usage_advanced.xml @@ -21,6 +21,18 @@ android:title="@string/advanced_battery_title" settings:keywords="@string/keywords_battery_usage"> + + + + + + mHourlyViewModels; private OnBatteryUsageUpdatedListener mOnBatteryUsageUpdatedListener; private OnScreenOnTimeUpdatedListener mOnScreenOnTimeUpdatedListener; + private OnBatteryTipsUpdatedListener mOnBatteryTipsUpdatedListener; private final SettingsActivity mActivity; private final MetricsFeatureProvider mMetricsFeatureProvider; @@ -209,6 +224,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mOnScreenOnTimeUpdatedListener = listener; } + void setOnBatteryTipsUpdatedListener(OnBatteryTipsUpdatedListener listener) { + mOnBatteryTipsUpdatedListener = listener; + } + void setBatteryHistoryMap( final Map> batteryHistoryMap) { Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null" @@ -344,6 +363,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated( slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty()); + + if (mOnBatteryTipsUpdatedListener != null) { + mOnBatteryTipsUpdatedListener.onBatteryTipsUpdated(null, null); + } } return true; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java new file mode 100644 index 00000000000..661d7c8aca3 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 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.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.TextView; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; + +import com.google.android.material.button.MaterialButton; + +/** + * A preference for displaying the battery tips card view. + */ +public class BatteryTipsCardPreference extends Preference implements View.OnClickListener { + + private static final String TAG = "BatteryTipsCardPreference"; + + private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; + + private MaterialButton mActionButton; + private ImageButton mDismissButton; + private ImageButton mThumbUpButton; + private ImageButton mThumbDownButton; + private CharSequence mTitle; + private CharSequence mSummary; + + public BatteryTipsCardPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.battery_tips_card); + setSelectable(false); + mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context); + } + + @Override + public void setTitle(CharSequence title) { + mTitle = title; + notifyChanged(); + } + + @Override + public void setSummary(CharSequence summary) { + mSummary = summary; + notifyChanged(); + } + + @Override + public void onClick(View view) { + // TODO: replace with the settings anomaly obtained from detectSettingsAnomaly(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + + ((TextView) view.findViewById(R.id.title)).setText(mTitle); + ((TextView) view.findViewById(R.id.summary)).setText(mSummary); + + mActionButton = (MaterialButton) view.findViewById(R.id.action_button); + mActionButton.setOnClickListener(this); + mDismissButton = (ImageButton) view.findViewById(R.id.dismiss_button); + mDismissButton.setOnClickListener(this); + + if (!mPowerUsageFeatureProvider.isBatteryTipsFeedbackEnabled()) { + return; + } + view.findViewById(R.id.tips_card) + .setBackgroundResource(R.drawable.battery_tips_half_rounded_top_bg); + view.findViewById(R.id.feedback_card).setVisibility(View.VISIBLE); + + mThumbUpButton = (ImageButton) view.findViewById(R.id.thumb_up); + mThumbUpButton.setOnClickListener(this); + mThumbDownButton = (ImageButton) view.findViewById(R.id.thumb_down); + mThumbDownButton.setOnClickListener(this); + } +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java new file mode 100644 index 00000000000..bcedd4f5794 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; + +/** Controls the update for battery tips card */ +public class BatteryTipsController extends BasePreferenceController { + + private static final String TAG = "BatteryTipsController"; + private static final String ROOT_PREFERENCE_KEY = "battery_tips_category"; + private static final String CARD_PREFERENCE_KEY = "battery_tips_card"; + + private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; + + private Context mPrefContext; + private BatteryTipsCardPreference mCardPreference; + + public BatteryTipsController(Context context) { + super(context, ROOT_PREFERENCE_KEY); + mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPrefContext = screen.getContext(); + mCardPreference = screen.findPreference(CARD_PREFERENCE_KEY); + } + + /** + * Update the card visibility and contents. + * @param title a string not extend 2 lines. + * @param summary a string not extend 10 lines. + */ + // TODO: replace parameters with SettingsAnomaly Data Proto + public void handleBatteryTipsCardUpdated(String title, String summary) { + if (!mPowerUsageFeatureProvider.isBatteryTipsEnabled()) { + mCardPreference.setVisible(false); + return; + } + if (title == null || summary == null) { + mCardPreference.setVisible(false); + return; + } + mCardPreference.setTitle(title); + mCardPreference.setSummary(summary); + mCardPreference.setVisible(true); + } + +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index 7c4478e66c6..9a8680e2244 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -34,6 +34,8 @@ import androidx.loader.content.Loader; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.fuelgauge.BatteryBroadcastReceiver; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -143,6 +145,16 @@ public class PowerUsageAdvanced extends PowerUsageBase { controllers.add(screenOnTimeController); controllers.add(batteryUsageBreakdownController); setBatteryChartPreferenceController(); + + final PowerUsageFeatureProvider powerUsageFeatureProvider = + FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); + if (powerUsageFeatureProvider.isBatteryTipsEnabled()) { + BatteryTipsController batteryTipsController = new BatteryTipsController(context); + mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener( + batteryTipsController::handleBatteryTipsCardUpdated); + controllers.add(batteryTipsController); + } + return controllers; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java index 1a43dbbe8d2..c9591a51b08 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java @@ -67,6 +67,15 @@ public class PowerUsageFeatureProviderImplTest { assertThat(mPowerFeatureProvider.isBatteryUsageEnabled()).isTrue(); } + @Test + public void testIsBatteryTipsEnabled_returnFalse() { + assertThat(mPowerFeatureProvider.isBatteryTipsEnabled()).isFalse(); + } + + @Test + public void testIsBatteryTipsFeedbackEnabled_returnTrue() { + assertThat(mPowerFeatureProvider.isBatteryTipsFeedbackEnabled()).isTrue(); + } @Test public void testGetBatteryUsageListConsumePowerThreshold_return0() { assertThat(mPowerFeatureProvider.getBatteryUsageListConsumePowerThreshold()).isEqualTo(0.0); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java new file mode 100644 index 00000000000..6f9a47416ae --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public final class BatteryTipsCardPreferenceTest { + + private Context mContext; + private BatteryTipsCardPreference mBatteryTipsCardPreference; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mBatteryTipsCardPreference = new BatteryTipsCardPreference(mContext, /*attrs=*/ null); + } + + @Test + public void constructor_returnExpectedResult() { + assertThat(mBatteryTipsCardPreference.getLayoutResource()).isEqualTo( + R.layout.battery_tips_card); + } +} From 69f46a72fc7867b9dc003b1089b0920f36aa56e4 Mon Sep 17 00:00:00 2001 From: ykhung Date: Tue, 1 Aug 2023 17:59:44 +0800 Subject: [PATCH 4/4] Update the incompatible charging banner tip icon Update the incompatible banner tip icon based on the UX update: https://screenshot.googleplex.com/8ApTNJnsxRRufjQ Fix: 246960554 Test: make test RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.fuelgauge Change-Id: I060554c17b3825b38004fb643c0a0cdf5fdbed4d --- res/drawable/ic_battery_charger.xml | 25 +++++++++++++++++++ .../BatterySettingsMigrateChecker.java | 5 ++-- .../tips/IncompatibleChargerTip.java | 2 +- .../tips/IncompatibleChargerTipTest.java | 2 +- 4 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 res/drawable/ic_battery_charger.xml diff --git a/res/drawable/ic_battery_charger.xml b/res/drawable/ic_battery_charger.xml new file mode 100644 index 00000000000..4406a568a3d --- /dev/null +++ b/res/drawable/ic_battery_charger.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java index 4b9e6efaff0..8697e438888 100644 --- a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java +++ b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java @@ -16,8 +16,8 @@ package com.android.settings.fuelgauge; -import android.content.ContentResolver; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.provider.Settings; @@ -25,8 +25,6 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; -import com.android.settings.R; -import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry; import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController; import com.android.settingslib.fuelgauge.BatterySaverUtils; @@ -41,6 +39,7 @@ public final class BatterySettingsMigrateChecker extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + Log.d(TAG, "onReceive: " + intent + " owner: " + BatteryBackupHelper.isOwner()); if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) && BatteryBackupHelper.isOwner()) { diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java index 1c5616f8675..48cfb7a4889 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java @@ -52,7 +52,7 @@ public final class IncompatibleChargerTip extends BatteryTip { @Override public int getIconId() { - return R.drawable.ic_battery_alert_theme; + return R.drawable.ic_battery_charger; } @Override diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java index a5f1ab3f531..9f6e4e32a71 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java @@ -85,7 +85,7 @@ public final class IncompatibleChargerTipTest { @Test public void getIcon_showIcon() { assertThat(mIncompatibleChargerTip.getIconId()) - .isEqualTo(R.drawable.ic_battery_alert_theme); + .isEqualTo(R.drawable.ic_battery_charger); } @Test