OSDN Git Service

Merge "Fix crash in anomaly job service" into pi-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / fuelgauge / batterytip / AnomalyDetectionJobService.java
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.settings.fuelgauge.batterytip;
18
19 import static android.os.StatsDimensionsValue.INT_VALUE_TYPE;
20 import static android.os.StatsDimensionsValue.TUPLE_VALUE_TYPE;
21
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;
45
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;
56
57 import java.util.ArrayList;
58 import java.util.List;
59 import java.util.concurrent.TimeUnit;
60
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;
65     @VisibleForTesting
66     static final int UID_NULL = -1;
67     @VisibleForTesting
68     static final int STATSD_UID_FILED = 1;
69     @VisibleForTesting
70     static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(30);
71
72     private final Object mLock = new Object();
73     @GuardedBy("mLock")
74     private boolean mIsJobCanceled = false;
75
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);
83
84         if (jobScheduler.enqueue(jobBuilder.build(), new JobWorkItem(intent))
85                 != JobScheduler.RESULT_SUCCESS) {
86             Log.i(TAG, "Anomaly detection job service enqueue failed.");
87         }
88     }
89
90     @Override
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);
108
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());
114
115                 completeWork(params, item);
116             }
117         });
118
119         return true;
120     }
121
122     @Override
123     public boolean onStopJob(JobParameters jobParameters) {
124         synchronized (mLock) {
125             mIsJobCanceled = true;
126         }
127         return true; // Need to reschedule
128     }
129
130     @VisibleForTesting
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());
148
149         try {
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);
158
159             final boolean anomalyDetected;
160             if (isExcessiveBackgroundAnomaly(anomalyInfo)) {
161                 anomalyDetected = batteryUtils.isPreOApp(packageName)
162                         && batteryUtils.isAppHeavilyUsed(batteryStatsHelper, userManager, uid,
163                         policy.excessiveBgDrainPercentage);
164             } else {
165                 anomalyDetected = true;
166             }
167
168             if (anomalyDetected) {
169                 if (batteryUtils.shouldHideAnomaly(powerWhitelistBackend, uid)) {
170                     metricsFeatureProvider.action(context,
171                             MetricsProto.MetricsEvent.ACTION_ANOMALY_IGNORED,
172                             packageName,
173                             Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT,
174                                     anomalyInfo.anomalyType),
175                             Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
176                                     versionCode));
177                 } else {
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,
184                                 timeMs);
185                     } else {
186                         databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType,
187                                 AnomalyDatabaseHelper.State.NEW,
188                                 timeMs);
189                     }
190                     metricsFeatureProvider.action(context,
191                             MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED,
192                             packageName,
193                             Pair.create(MetricsProto.MetricsEvent.FIELD_ANOMALY_TYPE,
194                                     anomalyInfo.anomalyType),
195                             Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
196                                     versionCode));
197                 }
198             }
199         } catch (NullPointerException | IndexOutOfBoundsException e) {
200             Log.e(TAG, "Parse stats dimensions value error.", e);
201         }
202     }
203
204     /**
205      * Extract the uid from {@link StatsDimensionsValue}
206      *
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|}|}|}
211      */
212     @VisibleForTesting
213     int extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue) {
214         if (statsDimensionsValue == null) {
215             return UID_NULL;
216         }
217         if (statsDimensionsValue.isValueType(INT_VALUE_TYPE)
218                 && statsDimensionsValue.getField() == STATSD_UID_FILED) {
219             // Find out the real uid
220             return statsDimensionsValue.getIntValue();
221         }
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) {
227                     return uid;
228                 }
229             }
230         }
231
232         return UID_NULL;
233     }
234
235     private boolean isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo) {
236         return anomalyInfo.anomalyType
237                 == StatsManagerConfig.AnomalyType.EXCESSIVE_BACKGROUND_SERVICE;
238     }
239
240     @VisibleForTesting
241     JobWorkItem dequeueWork(JobParameters parameters) {
242         synchronized (mLock) {
243             if (mIsJobCanceled) {
244                 return null;
245             }
246
247             return parameters.dequeueWork();
248         }
249     }
250
251     @VisibleForTesting
252     void completeWork(JobParameters parameters, JobWorkItem item) {
253         synchronized (mLock) {
254             if (mIsJobCanceled) {
255                 return;
256             }
257
258             parameters.completeWork(item);
259         }
260     }
261 }