From 3ee28c810d29e21a2d5d1ce76e5f2e9aede4f727 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 20 Feb 2018 15:07:26 -0800 Subject: [PATCH 1/2] Add auto restriction for excessive background If it is excessive bg anomaly and auto restriction is on, then restrict the anomaly in receiver and store it in database. Also in this cl we move the anomaly logic to a JobService, so all works are done in a background thread and won't interfere the main thread. Bug: 72385333 Test: RunSettingsRoboTests && Will add auto restriction test once robo framework is updated(b/73172999) Change-Id: Id0ec5fb449ce26bf19a292bcbe63838d621cfd8e --- Android.mk | 1 + AndroidManifest.xml | 3 + res/values/ids.xml | 1 + .../AnomalyDetectionJobService.java | 169 ++++++++++++++++++ .../batterytip/AnomalyDetectionReceiver.java | 43 +---- .../batterytip/BatteryDatabaseManager.java | 13 +- .../batterytip/StatsManagerConfig.java | 48 +++++ .../fuelgauge/BatteryDatabaseManagerTest.java | 13 +- .../AnomalyDetectionJobServiceTest.java | 59 ++++++ 9 files changed, 304 insertions(+), 46 deletions(-) create mode 100644 src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java diff --git a/Android.mk b/Android.mk index e816dfd3d3a..61851cf1b6d 100644 --- a/Android.mk +++ b/Android.mk @@ -40,6 +40,7 @@ LOCAL_JAVA_LIBRARIES := \ LOCAL_STATIC_JAVA_LIBRARIES := \ android-arch-lifecycle-runtime \ android-arch-lifecycle-extensions \ + guava \ jsr305 \ settings-logtags \ diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9332ac86d3e..d6a194ac142 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3309,6 +3309,9 @@ + + diff --git a/res/values/ids.xml b/res/values/ids.xml index d5c9291f2fb..76322ffaed6 100644 --- a/res/values/ids.xml +++ b/res/values/ids.xml @@ -19,6 +19,7 @@ + diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java new file mode 100644 index 00000000000..19fc0d58559 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2018 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.batterytip; + +import static android.os.StatsDimensionsValue.INT_VALUE_TYPE; +import static android.os.StatsDimensionsValue.TUPLE_VALUE_TYPE; + +import android.app.AppOpsManager; +import android.app.StatsManager; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.app.job.JobWorkItem; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.StatsDimensionsValue; +import android.os.SystemPropertiesProto; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settingslib.utils.ThreadUtils; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** A JobService to store anomaly data to anomaly database */ +public class AnomalyDetectionJobService extends JobService { + private static final String TAG = "AnomalyDetectionService"; + private static final int UID_NULL = 0; + private static final int STATSD_UID_FILED = 1; + private static final int ON = 1; + + @VisibleForTesting + static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(30); + + public static void scheduleAnomalyDetection(Context context, Intent intent) { + final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + final ComponentName component = new ComponentName(context, + AnomalyDetectionJobService.class); + final JobInfo.Builder jobBuilder = + new JobInfo.Builder(R.id.job_anomaly_detection, component) + .setOverrideDeadline(MAX_DELAY_MS); + + if (jobScheduler.enqueue(jobBuilder.build(), new JobWorkItem(intent)) + != JobScheduler.RESULT_SUCCESS) { + Log.i(TAG, "Anomaly detection job service enqueue failed."); + } + } + + @Override + public boolean onStartJob(JobParameters params) { + ThreadUtils.postOnBackgroundThread(() -> { + final BatteryDatabaseManager batteryDatabaseManager = + BatteryDatabaseManager.getInstance(this); + final BatteryTipPolicy policy = new BatteryTipPolicy(this); + final BatteryUtils batteryUtils = BatteryUtils.getInstance(this); + final ContentResolver contentResolver = getContentResolver(); + + for (JobWorkItem item = params.dequeueWork(); item != null; + item = params.dequeueWork()) { + saveAnomalyToDatabase(batteryDatabaseManager, batteryUtils, policy, contentResolver, + item.getIntent().getExtras()); + } + jobFinished(params, false /* wantsReschedule */); + }); + + return true; + } + + @Override + public boolean onStopJob(JobParameters jobParameters) { + return false; + } + + @VisibleForTesting + void saveAnomalyToDatabase(BatteryDatabaseManager databaseManager, + BatteryUtils batteryUtils, BatteryTipPolicy policy, ContentResolver contentResolver, + Bundle bundle) { + // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|} + final StatsDimensionsValue intentDimsValue = + bundle.getParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE); + final long subscriptionId = bundle.getLong(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, + -1); + final long timeMs = bundle.getLong(AnomalyDetectionReceiver.KEY_ANOMALY_TIMESTAMP, + System.currentTimeMillis()); + Log.i(TAG, "Extra stats value: " + intentDimsValue.toString()); + + try { + final int uid = extractUidFromStatsDimensionsValue(intentDimsValue); + final int anomalyType = StatsManagerConfig.getAnomalyTypeFromSubscriptionId( + subscriptionId); + final boolean smartBatteryOn = Settings.Global.getInt(contentResolver, + Settings.Global.APP_STANDBY_ENABLED, ON) == ON; + final String packageName = batteryUtils.getPackageName(uid); + + if (anomalyType == StatsManagerConfig.AnomalyType.EXCESSIVE_BG) { + // TODO(b/72385333): check battery percentage draining in batterystats + if (batteryUtils.isLegacyApp(packageName)) { + Log.e(TAG, "Excessive detected uid=" + uid); + batteryUtils.setForceAppStandby(uid, packageName, + AppOpsManager.MODE_IGNORED); + databaseManager.insertAnomaly(packageName, anomalyType, + smartBatteryOn + ? AnomalyDatabaseHelper.State.AUTO_HANDLED + : AnomalyDatabaseHelper.State.NEW, + timeMs); + } + } else { + databaseManager.insertAnomaly(packageName, anomalyType, + AnomalyDatabaseHelper.State.NEW, timeMs); + } + } catch (NullPointerException | IndexOutOfBoundsException e) { + Log.e(TAG, "Parse stats dimensions value error.", e); + } + } + + /** + * Extract the uid from {@link StatsDimensionsValue} + * + * The uid dimension has the format: 1: inside the tuple list. Here are some examples: + * 1. Excessive bg anomaly: 27:{1:10089|} + * 2. Wakeup alarm anomaly: 35:{1:{1:{1:10013|}|}|} + * 3. Bluetooth anomaly: 3:{1:{1:{1:10140|}|}|} + */ + @VisibleForTesting + final int extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue) { + //TODO(b/73172999): Add robo test for this method + if (statsDimensionsValue == null) { + return UID_NULL; + } + if (statsDimensionsValue.isValueType(INT_VALUE_TYPE) + && statsDimensionsValue.getField() == STATSD_UID_FILED) { + // Find out the real uid + return statsDimensionsValue.getIntValue(); + } + if (statsDimensionsValue.isValueType(TUPLE_VALUE_TYPE)) { + final List values = statsDimensionsValue.getTupleValueList(); + for (int i = 0, size = values.size(); i < size; i++) { + int uid = extractUidFromStatsDimensionsValue(values.get(i)); + if (uid != UID_NULL) { + return uid; + } + } + } + + return UID_NULL; + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionReceiver.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionReceiver.java index 88f399ffcd5..0a24b001c40 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionReceiver.java +++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionReceiver.java @@ -20,59 +20,30 @@ import android.app.StatsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.StatsDimensionsValue; -import android.support.annotation.VisibleForTesting; +import android.os.Bundle; import android.util.Log; -import com.android.settings.fuelgauge.BatteryUtils; - -import java.util.List; - /** * Receive the anomaly info from {@link StatsManager} */ public class AnomalyDetectionReceiver extends BroadcastReceiver { private static final String TAG = "SettingsAnomalyReceiver"; + public static final String KEY_ANOMALY_TIMESTAMP = "key_anomaly_timestamp"; + @Override public void onReceive(Context context, Intent intent) { - final BatteryDatabaseManager databaseManager = BatteryDatabaseManager.getInstance(context); - final BatteryUtils batteryUtils = BatteryUtils.getInstance(context); final long configUid = intent.getLongExtra(StatsManager.EXTRA_STATS_CONFIG_UID, -1); final long configKey = intent.getLongExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, -1); final long subscriptionId = intent.getLongExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, -1); - Log.i(TAG, "Anomaly intent received. configUid = " + configUid + " configKey = " + configKey + " subscriptionId = " + subscriptionId); - saveAnomalyToDatabase(databaseManager, batteryUtils, intent); + final Bundle bundle = intent.getExtras(); + bundle.putLong(KEY_ANOMALY_TIMESTAMP, System.currentTimeMillis()); + + AnomalyDetectionJobService.scheduleAnomalyDetection(context, intent); AnomalyCleanUpJobService.scheduleCleanUp(context); } - - @VisibleForTesting - void saveAnomalyToDatabase(BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils - , Intent intent) { - // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|} - StatsDimensionsValue intentDimsValue = - intent.getParcelableExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE); - Log.i(TAG, "Extra stats value: " + intentDimsValue.toString()); - List intentTuple = intentDimsValue.getTupleValueList(); - - if (!intentTuple.isEmpty()) { - try { - // TODO(b/72385333): find more robust way to extract the uid. - final StatsDimensionsValue intentTupleValue = intentTuple.get(0) - .getTupleValueList().get(0).getTupleValueList().get(0); - final int uid = intentTupleValue.getIntValue(); - // TODD(b/72385333): extract anomaly type - final int anomalyType = 0; - final String packageName = batteryUtils.getPackageName(uid); - final long timeMs = System.currentTimeMillis(); - databaseManager.insertAnomaly(packageName, anomalyType, timeMs); - } catch (NullPointerException | IndexOutOfBoundsException e) { - Log.e(TAG, "Parse stats dimensions value error.", e); - } - } - } } diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java index 87c248820da..935d4932575 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java @@ -60,18 +60,19 @@ public class BatteryDatabaseManager { /** * Insert an anomaly log to database. - * - * @param packageName the package name of the app - * @param type the type of the anomaly - * @param timestampMs the time when it is happened + * @param packageName the package name of the app + * @param type the type of the anomaly + * @param anomalyState the state of the anomaly + * @param timestampMs the time when it is happened */ - public synchronized void insertAnomaly(String packageName, int type, long timestampMs) { + public synchronized void insertAnomaly(String packageName, int type, int anomalyState, + long timestampMs) { try (SQLiteDatabase db = mDatabaseHelper.getWritableDatabase()) { ContentValues values = new ContentValues(); values.put(PACKAGE_NAME, packageName); values.put(ANOMALY_TYPE, type); + values.put(ANOMALY_STATE, anomalyState); values.put(TIME_STAMP_MS, timestampMs); - values.put(ANOMALY_STATE, AnomalyDatabaseHelper.State.NEW); db.insert(TABLE_ANOMALY, null, values); } } diff --git a/src/com/android/settings/fuelgauge/batterytip/StatsManagerConfig.java b/src/com/android/settings/fuelgauge/batterytip/StatsManagerConfig.java index 3b5e97dd0f9..62eb7eebbbb 100644 --- a/src/com/android/settings/fuelgauge/batterytip/StatsManagerConfig.java +++ b/src/com/android/settings/fuelgauge/batterytip/StatsManagerConfig.java @@ -16,6 +16,16 @@ package com.android.settings.fuelgauge.batterytip; +import android.support.annotation.IntDef; + +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Map; + /** * This class provides all the configs needed if we want to use {@link android.app.StatsManager} */ @@ -30,4 +40,42 @@ public class StatsManagerConfig { * The key that represents subscriber, which is settings app. */ public static final long SUBSCRIBER_ID = 1; + + private static final Map ANOMALY_TYPE; + + private static final HashFunction HASH_FUNCTION = Hashing.sha256(); + + static { + ANOMALY_TYPE = new HashMap<>(); + ANOMALY_TYPE.put(hash("SUBSCRIPTION:SETTINGS_EXCESSIVE_BACKGROUND_SERVICE"), + AnomalyType.EXCESSIVE_BG); + ANOMALY_TYPE.put(hash("SUBSCRIPTION:SETTINGS_LONG_UNOPTIMIZED_BLE_SCAN"), + AnomalyType.BLUETOOTH_SCAN); + ANOMALY_TYPE.put(hash("SUBSCRIPTION:SETTINGS_EXCESSIVE_WAKEUPS_IN_BACKGROUND"), + AnomalyType.WAKEUP_ALARM); + ANOMALY_TYPE.put(hash("SUBSCRIPTION:SETTINGS_EXCESSIVE_WAKELOCK_ALL_SCREEN_OFF"), + AnomalyType.WAKE_LOCK); + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({AnomalyType.NULL, + AnomalyType.WAKE_LOCK, + AnomalyType.WAKEUP_ALARM, + AnomalyType.BLUETOOTH_SCAN, + AnomalyType.EXCESSIVE_BG}) + public @interface AnomalyType { + int NULL = -1; + int WAKE_LOCK = 0; + int WAKEUP_ALARM = 1; + int BLUETOOTH_SCAN = 2; + int EXCESSIVE_BG = 3; + } + + public static int getAnomalyTypeFromSubscriptionId(long subscriptionId) { + return ANOMALY_TYPE.getOrDefault(subscriptionId, AnomalyType.NULL); + } + + private static long hash(CharSequence value) { + return HASH_FUNCTION.hashUnencodedChars(value).asLong(); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java index e835e65a9b1..498cd587138 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java @@ -51,6 +51,7 @@ public class BatteryDatabaseManagerTest { private static long NOW = System.currentTimeMillis(); private static long ONE_DAY_BEFORE = NOW - DateUtils.DAY_IN_MILLIS; private static long TWO_DAYS_BEFORE = NOW - 2 * DateUtils.DAY_IN_MILLIS; + private Context mContext; private BatteryDatabaseManager mBatteryDatabaseManager; @@ -69,8 +70,10 @@ public class BatteryDatabaseManagerTest { @Test public void testAllFunctions() { - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_NEW, TYPE_NEW, NOW); - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_OLD, TYPE_OLD, TWO_DAYS_BEFORE); + mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_NEW, TYPE_NEW, + AnomalyDatabaseHelper.State.NEW, NOW); + mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_OLD, TYPE_OLD, + AnomalyDatabaseHelper.State.NEW, TWO_DAYS_BEFORE); // In database, it contains two record List totalAppInfos = mBatteryDatabaseManager.queryAllAnomalies(0 /* timeMsAfter */, @@ -96,8 +99,10 @@ public class BatteryDatabaseManagerTest { @Test public void testUpdateAnomalies_updateSuccessfully() { - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_NEW, TYPE_NEW, NOW); - mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_OLD, TYPE_OLD, NOW); + mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_NEW, TYPE_NEW, + AnomalyDatabaseHelper.State.NEW, NOW); + mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_OLD, TYPE_OLD, + AnomalyDatabaseHelper.State.NEW, NOW); final AppInfo appInfo = new AppInfo.Builder().setPackageName(PACKAGE_NAME_OLD).build(); final List updateAppInfos = new ArrayList<>(); updateAppInfos.add(appInfo); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java new file mode 100644 index 00000000000..48c99c5f2eb --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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.batterytip; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.robolectric.RuntimeEnvironment.application; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.Intent; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowJobScheduler; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class AnomalyDetectionJobServiceTest { + + @Test + public void testScheduleCleanUp() { + AnomalyDetectionJobService.scheduleAnomalyDetection(application, + new Intent()); + + ShadowJobScheduler shadowJobScheduler = Shadows.shadowOf( + application.getSystemService(JobScheduler.class)); + List pendingJobs = shadowJobScheduler.getAllPendingJobs(); + assertThat(pendingJobs).hasSize(1); + JobInfo pendingJob = pendingJobs.get(0); + assertThat(pendingJob.getId()).isEqualTo(R.id.job_anomaly_detection); + assertThat(pendingJob.getMaxExecutionDelayMillis()).isEqualTo( + TimeUnit.MINUTES.toMillis(30)); + } +} From 457fb842ebc6ff1616c09fcf5a7d71d24c818de2 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Thu, 22 Feb 2018 10:22:08 -0800 Subject: [PATCH 2/2] Add special check for excessive bg anomaly To check whether this app has battery usage more than x% Bug: 72385333 Test: RunSettingsRoboTests Change-Id: I87e6b01c866a053658f84ce3486120ae82963fd9 --- .../settings/fuelgauge/BatteryUtils.java | 37 ++++++++++++++++++- .../AnomalyDetectionJobService.java | 18 ++++++--- .../batterytip/BatteryTipPolicy.java | 12 ++++++ .../settings/fuelgauge/BatteryUtilsTest.java | 18 ++++++++- .../batterytip/BatteryTipPolicyTest.java | 6 ++- 5 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index da9b7059908..10bc85343e9 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -44,6 +44,7 @@ import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.utils.PowerUtil; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -69,6 +70,7 @@ public class BatteryUtils { int BACKGROUND = 2; int ALL = 3; } + private static final String TAG = "BatteryUtils"; private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5; @@ -81,6 +83,7 @@ public class BatteryUtils { private Context mContext; @VisibleForTesting PowerUsageFeatureProvider mPowerUsageFeatureProvider; + public static BatteryUtils getInstance(Context context) { if (sInstance == null || sInstance.isDataCorrupted()) { sInstance = new BatteryUtils(context); @@ -153,8 +156,7 @@ public class BatteryUtils { private long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) { final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); return getScreenUsageTimeMs(uid, which, rawRealTimeUs) - + PowerUtil.convertUsToMs( - getForegroundServiceTotalTimeUs(uid, rawRealTimeUs)); + + PowerUtil.convertUsToMs(getForegroundServiceTotalTimeUs(uid, rawRealTimeUs)); } /** @@ -349,6 +351,7 @@ public class BatteryUtils { /** * Calculate the screen usage time since last full charge. + * * @param batteryStatsHelper utility class that contains the screen usage data * @return time in millis */ @@ -500,5 +503,35 @@ public class BatteryUtils { return false; } + /** + * Check if the app represented by {@code uid} has battery usage more than {@code threshold} + * + * @param batteryStatsHelper used to check the battery usage + * @param userManager used to init the {@code batteryStatsHelper} + * @param uid represent the app + * @param threshold battery percentage threshold(e.g. 10 means 10% battery usage ) + * @return {@code true} if battery drain is more than the threshold + */ + public boolean isAppHeavilyUsed(BatteryStatsHelper batteryStatsHelper, UserManager userManager, + int uid, int threshold) { + initBatteryStatsHelper(batteryStatsHelper, null /* bundle */, userManager); + final int dischargeAmount = batteryStatsHelper.getStats().getDischargeAmount( + BatteryStats.STATS_SINCE_CHARGED); + List batterySippers = batteryStatsHelper.getUsageList(); + final double hiddenAmount = removeHiddenBatterySippers(batterySippers); + + for (int i = 0, size = batterySippers.size(); i < size; i++) { + final BatterySipper batterySipper = batterySippers.get(i); + if (batterySipper.getUid() == uid) { + final int percent = (int) calculateBatteryPercent( + batterySipper.totalPowerMah, batteryStatsHelper.getTotalPower(), + hiddenAmount, + dischargeAmount); + return percent >= threshold; + } + } + + return false; + } } diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java index 19fc0d58559..9d4f86f54c0 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java +++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java @@ -33,10 +33,12 @@ import android.content.Intent; import android.os.Bundle; import android.os.StatsDimensionsValue; import android.os.SystemPropertiesProto; +import android.os.UserManager; import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.util.Log; +import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settingslib.utils.ThreadUtils; @@ -76,10 +78,14 @@ public class AnomalyDetectionJobService extends JobService { final BatteryTipPolicy policy = new BatteryTipPolicy(this); final BatteryUtils batteryUtils = BatteryUtils.getInstance(this); final ContentResolver contentResolver = getContentResolver(); + final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(this, + true /* collectBatteryBroadcast */); + final UserManager userManager = getSystemService(UserManager.class); for (JobWorkItem item = params.dequeueWork(); item != null; item = params.dequeueWork()) { - saveAnomalyToDatabase(batteryDatabaseManager, batteryUtils, policy, contentResolver, + saveAnomalyToDatabase(batteryStatsHelper, userManager, batteryDatabaseManager, + batteryUtils, policy, contentResolver, item.getIntent().getExtras()); } jobFinished(params, false /* wantsReschedule */); @@ -94,9 +100,9 @@ public class AnomalyDetectionJobService extends JobService { } @VisibleForTesting - void saveAnomalyToDatabase(BatteryDatabaseManager databaseManager, - BatteryUtils batteryUtils, BatteryTipPolicy policy, ContentResolver contentResolver, - Bundle bundle) { + void saveAnomalyToDatabase(BatteryStatsHelper batteryStatsHelper, UserManager userManager, + BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils, + BatteryTipPolicy policy, ContentResolver contentResolver, Bundle bundle) { // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|} final StatsDimensionsValue intentDimsValue = bundle.getParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE); @@ -116,7 +122,9 @@ public class AnomalyDetectionJobService extends JobService { if (anomalyType == StatsManagerConfig.AnomalyType.EXCESSIVE_BG) { // TODO(b/72385333): check battery percentage draining in batterystats - if (batteryUtils.isLegacyApp(packageName)) { + if (batteryUtils.isLegacyApp(packageName) && batteryUtils.isAppHeavilyUsed( + batteryStatsHelper, userManager, uid, + policy.excessiveBgDrainPercentage)) { Log.e(TAG, "Excessive detected uid=" + uid); batteryUtils.setForceAppStandby(uid, packageName, AppOpsManager.MODE_IGNORED); diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java index 4a7b51d9ab0..fcdbb3a345f 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java @@ -44,6 +44,7 @@ public class BatteryTipPolicy { private static final String KEY_LOW_BATTERY_ENABLED = "low_battery_enabled"; private static final String KEY_LOW_BATTERY_HOUR = "low_battery_hour"; private static final String KEY_DATA_HISTORY_RETAIN_HOUR = "data_history_retain_hour"; + private static final String KEY_EXCESSIVE_BG_DRAIN_PERCENTAGE = "excessive_bg_drain_percentage"; /** * {@code true} if general battery tip is enabled @@ -151,6 +152,16 @@ public class BatteryTipPolicy { */ public final int dataHistoryRetainHour; + /** + * Battery drain percentage threshold for excessive background anomaly(i.e. 10%) + * + * This is an additional check for excessive background, to check whether battery drain + * for an app is larger than x% + * @see Settings.Global#BATTERY_TIP_CONSTANTS + * @see #KEY_EXCESSIVE_BG_DRAIN_PERCENTAGE + */ + public final int excessiveBgDrainPercentage; + private final KeyValueListParser mParser; public BatteryTipPolicy(Context context) { @@ -183,6 +194,7 @@ public class BatteryTipPolicy { lowBatteryEnabled = mParser.getBoolean(KEY_LOW_BATTERY_ENABLED, false); lowBatteryHour = mParser.getInt(KEY_LOW_BATTERY_HOUR, 16); dataHistoryRetainHour = mParser.getInt(KEY_DATA_HISTORY_RETAIN_HOUR, 72); + excessiveBgDrainPercentage = mParser.getInt(KEY_EXCESSIVE_BG_DRAIN_PERCENTAGE, 10); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java index 6bc6ee71664..9d3fb7a436a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java @@ -177,9 +177,9 @@ public class BatteryUtilsTest { mHighApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O; mLowApplicationInfo.targetSdkVersion = Build.VERSION_CODES.L; - mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE; + doReturn(UID).when(mNormalBatterySipper).getUid(); mWifiBatterySipper.drainType = BatterySipper.DrainType.WIFI; mWifiBatterySipper.totalPowerMah = BATTERY_WIFI_USAGE; @@ -216,6 +216,10 @@ public class BatteryUtilsTest { mUsageList.add(mScreenBatterySipper); mUsageList.add(mCellBatterySipper); doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList(); + doReturn(TOTAL_BATTERY_USAGE + BATTERY_SCREEN_USAGE).when( + mBatteryStatsHelper).getTotalPower(); + when(mBatteryStatsHelper.getStats().getDischargeAmount(anyInt())).thenReturn( + DISCHARGE_AMOUNT); } @Test @@ -547,4 +551,16 @@ public class BatteryUtilsTest { verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID, HIGH_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); } + + @Test + public void testIsAppHeavilyUsed_usageMoreThanThreshold_returnTrue() { + assertThat(mBatteryUtils.isAppHeavilyUsed(mBatteryStatsHelper, mUserManager, UID, + 10 /* threshold */ )).isTrue(); + } + + @Test + public void testIsAppHeavilyUsed_usageLessThanThreshold_returnFalse() { + assertThat(mBatteryUtils.isAppHeavilyUsed(mBatteryStatsHelper, mUserManager, UID, + DISCHARGE_AMOUNT /* threshold */ )).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java index f36acee209a..abf04cd1440 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java @@ -50,7 +50,8 @@ public class BatteryTipPolicyTest { + ",reduced_battery_percent=30" + ",low_battery_enabled=false" + ",low_battery_hour=10" - + ",data_history_retain_hour=24"; + + ",data_history_retain_hour=24" + + ",excessive_bg_drain_percentage=25"; private Context mContext; @Before @@ -78,6 +79,7 @@ public class BatteryTipPolicyTest { assertThat(batteryTipPolicy.lowBatteryEnabled).isFalse(); assertThat(batteryTipPolicy.lowBatteryHour).isEqualTo(10); assertThat(batteryTipPolicy.dataHistoryRetainHour).isEqualTo(24); + assertThat(batteryTipPolicy.excessiveBgDrainPercentage).isEqualTo(25); } @Test @@ -100,6 +102,6 @@ public class BatteryTipPolicyTest { assertThat(batteryTipPolicy.lowBatteryEnabled).isFalse(); assertThat(batteryTipPolicy.lowBatteryHour).isEqualTo(16); assertThat(batteryTipPolicy.dataHistoryRetainHour).isEqualTo(72); + assertThat(batteryTipPolicy.excessiveBgDrainPercentage).isEqualTo(10); } - }