2 * Copyright (C) 2018 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings.fuelgauge.batterytip;
19 import static android.os.StatsDimensionsValue.INT_VALUE_TYPE;
20 import static android.os.StatsDimensionsValue.TUPLE_VALUE_TYPE;
22 import android.app.AppOpsManager;
23 import android.app.StatsManager;
24 import android.app.job.JobInfo;
25 import android.app.job.JobParameters;
26 import android.app.job.JobScheduler;
27 import android.app.job.JobService;
28 import android.app.job.JobWorkItem;
29 import android.content.ComponentName;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.os.Bundle;
36 import android.os.Process;
37 import android.os.StatsDimensionsValue;
38 import android.os.UserHandle;
39 import android.os.UserManager;
40 import android.provider.Settings;
41 import android.support.annotation.VisibleForTesting;
42 import android.util.Log;
43 import android.util.Pair;
45 import com.android.internal.logging.nano.MetricsProto;
46 import com.android.internal.os.BatteryStatsHelper;
47 import com.android.internal.util.ArrayUtils;
48 import com.android.settings.R;
49 import com.android.settings.fuelgauge.BatteryUtils;
50 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
51 import com.android.settings.overlay.FeatureFactory;
52 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
53 import com.android.settingslib.fuelgauge.PowerWhitelistBackend;
54 import com.android.settingslib.utils.ThreadUtils;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.concurrent.TimeUnit;
60 /** A JobService to store anomaly data to anomaly database */
61 public class AnomalyDetectionJobService extends JobService {
62 private static final String TAG = "AnomalyDetectionService";
63 private static final int ON = 1;
65 static final int UID_NULL = -1;
67 static final int STATSD_UID_FILED = 1;
70 static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(30);
72 public static void scheduleAnomalyDetection(Context context, Intent intent) {
73 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
74 final ComponentName component = new ComponentName(context,
75 AnomalyDetectionJobService.class);
76 final JobInfo.Builder jobBuilder =
77 new JobInfo.Builder(R.integer.job_anomaly_detection, component)
78 .setOverrideDeadline(MAX_DELAY_MS);
80 if (jobScheduler.enqueue(jobBuilder.build(), new JobWorkItem(intent))
81 != JobScheduler.RESULT_SUCCESS) {
82 Log.i(TAG, "Anomaly detection job service enqueue failed.");
87 public boolean onStartJob(JobParameters params) {
88 ThreadUtils.postOnBackgroundThread(() -> {
89 final Context context = AnomalyDetectionJobService.this;
90 final BatteryDatabaseManager batteryDatabaseManager =
91 BatteryDatabaseManager.getInstance(this);
92 final BatteryTipPolicy policy = new BatteryTipPolicy(this);
93 final BatteryUtils batteryUtils = BatteryUtils.getInstance(this);
94 final ContentResolver contentResolver = getContentResolver();
95 final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(this,
96 true /* collectBatteryBroadcast */);
97 final UserManager userManager = getSystemService(UserManager.class);
98 final PowerWhitelistBackend powerWhitelistBackend = PowerWhitelistBackend.getInstance();
99 final PowerUsageFeatureProvider powerUsageFeatureProvider = FeatureFactory
100 .getFactory(this).getPowerUsageFeatureProvider(this);
101 final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory
102 .getFactory(this).getMetricsFeatureProvider();
104 for (JobWorkItem item = params.dequeueWork(); item != null;
105 item = params.dequeueWork()) {
106 saveAnomalyToDatabase(context, batteryStatsHelper, userManager,
107 batteryDatabaseManager, batteryUtils, policy, powerWhitelistBackend,
108 contentResolver, powerUsageFeatureProvider, metricsFeatureProvider,
109 item.getIntent().getExtras());
111 jobFinished(params, false /* wantsReschedule */);
118 public boolean onStopJob(JobParameters jobParameters) {
123 void saveAnomalyToDatabase(Context context, BatteryStatsHelper batteryStatsHelper,
124 UserManager userManager,
125 BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils,
126 BatteryTipPolicy policy, PowerWhitelistBackend powerWhitelistBackend,
127 ContentResolver contentResolver, PowerUsageFeatureProvider powerUsageFeatureProvider,
128 MetricsFeatureProvider metricsFeatureProvider, Bundle bundle) {
129 // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|}
130 final StatsDimensionsValue intentDimsValue =
131 bundle.getParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE);
132 final long timeMs = bundle.getLong(AnomalyDetectionReceiver.KEY_ANOMALY_TIMESTAMP,
133 System.currentTimeMillis());
134 final ArrayList<String> cookies = bundle.getStringArrayList(
135 StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES);
136 final AnomalyInfo anomalyInfo = new AnomalyInfo(
137 !ArrayUtils.isEmpty(cookies) ? cookies.get(0) : "");
138 final PackageManager packageManager = context.getPackageManager();
139 Log.i(TAG, "Extra stats value: " + intentDimsValue.toString());
142 final int uid = extractUidFromStatsDimensionsValue(intentDimsValue);
143 final boolean autoFeatureOn = powerUsageFeatureProvider.isSmartBatterySupported()
144 ? Settings.Global.getInt(contentResolver,
145 Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, ON) == ON
146 : Settings.Global.getInt(contentResolver,
147 Settings.Global.APP_AUTO_RESTRICTION_ENABLED, ON) == ON;
148 final String packageName = batteryUtils.getPackageName(uid);
149 final long versionCode = batteryUtils.getAppLongVersionCode(packageName);
151 final boolean anomalyDetected;
152 if (isExcessiveBackgroundAnomaly(anomalyInfo)) {
153 anomalyDetected = batteryUtils.isPreOApp(packageName)
154 && batteryUtils.isAppHeavilyUsed(batteryStatsHelper, userManager, uid,
155 policy.excessiveBgDrainPercentage);
157 anomalyDetected = true;
160 if (anomalyDetected) {
161 if (batteryUtils.shouldHideAnomaly(powerWhitelistBackend, uid)) {
162 metricsFeatureProvider.action(context,
163 MetricsProto.MetricsEvent.ACTION_ANOMALY_IGNORED,
165 Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT,
166 anomalyInfo.anomalyType),
167 Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
170 if (autoFeatureOn && anomalyInfo.autoRestriction) {
171 // Auto restrict this app
172 batteryUtils.setForceAppStandby(uid, packageName,
173 AppOpsManager.MODE_IGNORED);
174 databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType,
175 AnomalyDatabaseHelper.State.AUTO_HANDLED,
178 databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType,
179 AnomalyDatabaseHelper.State.NEW,
182 metricsFeatureProvider.action(context,
183 MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED,
185 Pair.create(MetricsProto.MetricsEvent.FIELD_ANOMALY_TYPE,
186 anomalyInfo.anomalyType),
187 Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
191 } catch (NullPointerException | IndexOutOfBoundsException e) {
192 Log.e(TAG, "Parse stats dimensions value error.", e);
197 * Extract the uid from {@link StatsDimensionsValue}
199 * The uid dimension has the format: 1:<int> inside the tuple list. Here are some examples:
200 * 1. Excessive bg anomaly: 27:{1:10089|}
201 * 2. Wakeup alarm anomaly: 35:{1:{1:{1:10013|}|}|}
202 * 3. Bluetooth anomaly: 3:{1:{1:{1:10140|}|}|}
205 int extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue) {
206 if (statsDimensionsValue == null) {
209 if (statsDimensionsValue.isValueType(INT_VALUE_TYPE)
210 && statsDimensionsValue.getField() == STATSD_UID_FILED) {
211 // Find out the real uid
212 return statsDimensionsValue.getIntValue();
214 if (statsDimensionsValue.isValueType(TUPLE_VALUE_TYPE)) {
215 final List<StatsDimensionsValue> values = statsDimensionsValue.getTupleValueList();
216 for (int i = 0, size = values.size(); i < size; i++) {
217 int uid = extractUidFromStatsDimensionsValue(values.get(i));
218 if (uid != UID_NULL) {
227 private boolean isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo) {
228 return anomalyInfo.anomalyType
229 == StatsManagerConfig.AnomalyType.EXCESSIVE_BACKGROUND_SERVICE;