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.GuardedBy;
42 import android.support.annotation.VisibleForTesting;
43 import android.util.Log;
44 import android.util.Pair;
46 import com.android.internal.logging.nano.MetricsProto;
47 import com.android.internal.os.BatteryStatsHelper;
48 import com.android.internal.util.ArrayUtils;
49 import com.android.settings.R;
50 import com.android.settings.fuelgauge.BatteryUtils;
51 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
52 import com.android.settings.overlay.FeatureFactory;
53 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
54 import com.android.settingslib.fuelgauge.PowerWhitelistBackend;
55 import com.android.settingslib.utils.ThreadUtils;
57 import java.util.ArrayList;
58 import java.util.List;
59 import java.util.concurrent.TimeUnit;
61 /** A JobService to store anomaly data to anomaly database */
62 public class AnomalyDetectionJobService extends JobService {
63 private static final String TAG = "AnomalyDetectionService";
64 private static final int ON = 1;
66 static final int UID_NULL = -1;
68 static final int STATSD_UID_FILED = 1;
70 static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(30);
72 private final Object mLock = new Object();
74 private boolean mIsJobCanceled = false;
76 public static void scheduleAnomalyDetection(Context context, Intent intent) {
77 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
78 final ComponentName component = new ComponentName(context,
79 AnomalyDetectionJobService.class);
80 final JobInfo.Builder jobBuilder =
81 new JobInfo.Builder(R.integer.job_anomaly_detection, component)
82 .setOverrideDeadline(MAX_DELAY_MS);
84 if (jobScheduler.enqueue(jobBuilder.build(), new JobWorkItem(intent))
85 != JobScheduler.RESULT_SUCCESS) {
86 Log.i(TAG, "Anomaly detection job service enqueue failed.");
91 public boolean onStartJob(JobParameters params) {
92 ThreadUtils.postOnBackgroundThread(() -> {
93 final Context context = AnomalyDetectionJobService.this;
94 final BatteryDatabaseManager batteryDatabaseManager =
95 BatteryDatabaseManager.getInstance(this);
96 final BatteryTipPolicy policy = new BatteryTipPolicy(this);
97 final BatteryUtils batteryUtils = BatteryUtils.getInstance(this);
98 final ContentResolver contentResolver = getContentResolver();
99 final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(this,
100 true /* collectBatteryBroadcast */);
101 final UserManager userManager = getSystemService(UserManager.class);
102 final PowerWhitelistBackend powerWhitelistBackend = PowerWhitelistBackend.getInstance();
103 final PowerUsageFeatureProvider powerUsageFeatureProvider = FeatureFactory
104 .getFactory(this).getPowerUsageFeatureProvider(this);
105 final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory
106 .getFactory(this).getMetricsFeatureProvider();
107 batteryUtils.initBatteryStatsHelper(batteryStatsHelper, null /* bundle */, userManager);
109 for (JobWorkItem item = dequeueWork(params); item != null; item = dequeueWork(params)) {
110 saveAnomalyToDatabase(context, batteryStatsHelper, userManager,
111 batteryDatabaseManager, batteryUtils, policy, powerWhitelistBackend,
112 contentResolver, powerUsageFeatureProvider, metricsFeatureProvider,
113 item.getIntent().getExtras());
115 completeWork(params, item);
123 public boolean onStopJob(JobParameters jobParameters) {
124 synchronized (mLock) {
125 mIsJobCanceled = true;
127 return true; // Need to reschedule
131 void saveAnomalyToDatabase(Context context, BatteryStatsHelper batteryStatsHelper,
132 UserManager userManager,
133 BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils,
134 BatteryTipPolicy policy, PowerWhitelistBackend powerWhitelistBackend,
135 ContentResolver contentResolver, PowerUsageFeatureProvider powerUsageFeatureProvider,
136 MetricsFeatureProvider metricsFeatureProvider, Bundle bundle) {
137 // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|}
138 final StatsDimensionsValue intentDimsValue =
139 bundle.getParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE);
140 final long timeMs = bundle.getLong(AnomalyDetectionReceiver.KEY_ANOMALY_TIMESTAMP,
141 System.currentTimeMillis());
142 final ArrayList<String> cookies = bundle.getStringArrayList(
143 StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES);
144 final AnomalyInfo anomalyInfo = new AnomalyInfo(
145 !ArrayUtils.isEmpty(cookies) ? cookies.get(0) : "");
146 final PackageManager packageManager = context.getPackageManager();
147 Log.i(TAG, "Extra stats value: " + intentDimsValue.toString());
150 final int uid = extractUidFromStatsDimensionsValue(intentDimsValue);
151 final boolean autoFeatureOn = powerUsageFeatureProvider.isSmartBatterySupported()
152 ? Settings.Global.getInt(contentResolver,
153 Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, ON) == ON
154 : Settings.Global.getInt(contentResolver,
155 Settings.Global.APP_AUTO_RESTRICTION_ENABLED, ON) == ON;
156 final String packageName = batteryUtils.getPackageName(uid);
157 final long versionCode = batteryUtils.getAppLongVersionCode(packageName);
159 final boolean anomalyDetected;
160 if (isExcessiveBackgroundAnomaly(anomalyInfo)) {
161 anomalyDetected = batteryUtils.isPreOApp(packageName)
162 && batteryUtils.isAppHeavilyUsed(batteryStatsHelper, userManager, uid,
163 policy.excessiveBgDrainPercentage);
165 anomalyDetected = true;
168 if (anomalyDetected) {
169 if (batteryUtils.shouldHideAnomaly(powerWhitelistBackend, uid)) {
170 metricsFeatureProvider.action(context,
171 MetricsProto.MetricsEvent.ACTION_ANOMALY_IGNORED,
173 Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT,
174 anomalyInfo.anomalyType),
175 Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
178 if (autoFeatureOn && anomalyInfo.autoRestriction) {
179 // Auto restrict this app
180 batteryUtils.setForceAppStandby(uid, packageName,
181 AppOpsManager.MODE_IGNORED);
182 databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType,
183 AnomalyDatabaseHelper.State.AUTO_HANDLED,
186 databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType,
187 AnomalyDatabaseHelper.State.NEW,
190 metricsFeatureProvider.action(context,
191 MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED,
193 Pair.create(MetricsProto.MetricsEvent.FIELD_ANOMALY_TYPE,
194 anomalyInfo.anomalyType),
195 Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
199 } catch (NullPointerException | IndexOutOfBoundsException e) {
200 Log.e(TAG, "Parse stats dimensions value error.", e);
205 * Extract the uid from {@link StatsDimensionsValue}
207 * The uid dimension has the format: 1:<int> inside the tuple list. Here are some examples:
208 * 1. Excessive bg anomaly: 27:{1:10089|}
209 * 2. Wakeup alarm anomaly: 35:{1:{1:{1:10013|}|}|}
210 * 3. Bluetooth anomaly: 3:{1:{1:{1:10140|}|}|}
213 int extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue) {
214 if (statsDimensionsValue == null) {
217 if (statsDimensionsValue.isValueType(INT_VALUE_TYPE)
218 && statsDimensionsValue.getField() == STATSD_UID_FILED) {
219 // Find out the real uid
220 return statsDimensionsValue.getIntValue();
222 if (statsDimensionsValue.isValueType(TUPLE_VALUE_TYPE)) {
223 final List<StatsDimensionsValue> values = statsDimensionsValue.getTupleValueList();
224 for (int i = 0, size = values.size(); i < size; i++) {
225 int uid = extractUidFromStatsDimensionsValue(values.get(i));
226 if (uid != UID_NULL) {
235 private boolean isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo) {
236 return anomalyInfo.anomalyType
237 == StatsManagerConfig.AnomalyType.EXCESSIVE_BACKGROUND_SERVICE;
241 JobWorkItem dequeueWork(JobParameters parameters) {
242 synchronized (mLock) {
243 if (mIsJobCanceled) {
247 return parameters.dequeueWork();
252 void completeWork(JobParameters parameters, JobWorkItem item) {
253 synchronized (mLock) {
254 if (mIsJobCanceled) {
258 parameters.completeWork(item);