• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.Bundle;
34 import android.os.StatsDimensionsValue;
35 import android.os.UserManager;
36 import android.provider.Settings;
37 import android.support.annotation.GuardedBy;
38 import android.support.annotation.VisibleForTesting;
39 import android.util.Log;
40 import android.util.Pair;
41 
42 import com.android.internal.logging.nano.MetricsProto;
43 import com.android.internal.util.ArrayUtils;
44 import com.android.settings.R;
45 import com.android.settings.fuelgauge.BatteryUtils;
46 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
47 import com.android.settings.overlay.FeatureFactory;
48 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
49 import com.android.settingslib.fuelgauge.PowerWhitelistBackend;
50 import com.android.settingslib.utils.ThreadUtils;
51 
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.concurrent.TimeUnit;
55 
56 /** A JobService to store anomaly data to anomaly database */
57 public class AnomalyDetectionJobService extends JobService {
58     private static final String TAG = "AnomalyDetectionService";
59     private static final int ON = 1;
60     @VisibleForTesting
61     static final int UID_NULL = -1;
62     @VisibleForTesting
63     static final int STATSD_UID_FILED = 1;
64     @VisibleForTesting
65     static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(30);
66 
67     private final Object mLock = new Object();
68     @GuardedBy("mLock")
69     @VisibleForTesting
70     boolean mIsJobCanceled = false;
71 
scheduleAnomalyDetection(Context context, Intent intent)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);
79 
80         if (jobScheduler.enqueue(jobBuilder.build(), new JobWorkItem(intent))
81                 != JobScheduler.RESULT_SUCCESS) {
82             Log.i(TAG, "Anomaly detection job service enqueue failed.");
83         }
84     }
85 
86     @Override
onStartJob(JobParameters params)87     public boolean onStartJob(JobParameters params) {
88         synchronized (mLock) {
89             mIsJobCanceled = false;
90         }
91         ThreadUtils.postOnBackgroundThread(() -> {
92             final Context context = AnomalyDetectionJobService.this;
93             final BatteryDatabaseManager batteryDatabaseManager =
94                     BatteryDatabaseManager.getInstance(this);
95             final BatteryTipPolicy policy = new BatteryTipPolicy(this);
96             final BatteryUtils batteryUtils = BatteryUtils.getInstance(this);
97             final ContentResolver contentResolver = getContentResolver();
98             final UserManager userManager = getSystemService(UserManager.class);
99             final PowerWhitelistBackend powerWhitelistBackend =
100                     PowerWhitelistBackend.getInstance(context);
101             final PowerUsageFeatureProvider powerUsageFeatureProvider = FeatureFactory
102                     .getFactory(this).getPowerUsageFeatureProvider(this);
103             final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory
104                     .getFactory(this).getMetricsFeatureProvider();
105 
106             for (JobWorkItem item = dequeueWork(params); item != null; item = dequeueWork(params)) {
107                 saveAnomalyToDatabase(context, userManager,
108                         batteryDatabaseManager, batteryUtils, policy, powerWhitelistBackend,
109                         contentResolver, powerUsageFeatureProvider, metricsFeatureProvider,
110                         item.getIntent().getExtras());
111 
112                 completeWork(params, item);
113             }
114         });
115 
116         return true;
117     }
118 
119     @Override
onStopJob(JobParameters jobParameters)120     public boolean onStopJob(JobParameters jobParameters) {
121         synchronized (mLock) {
122             mIsJobCanceled = true;
123         }
124         return true; // Need to reschedule
125     }
126 
127     @VisibleForTesting
saveAnomalyToDatabase(Context context, UserManager userManager, BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils, BatteryTipPolicy policy, PowerWhitelistBackend powerWhitelistBackend, ContentResolver contentResolver, PowerUsageFeatureProvider powerUsageFeatureProvider, MetricsFeatureProvider metricsFeatureProvider, Bundle bundle)128     void saveAnomalyToDatabase(Context context, UserManager userManager,
129             BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils,
130             BatteryTipPolicy policy, PowerWhitelistBackend powerWhitelistBackend,
131             ContentResolver contentResolver, PowerUsageFeatureProvider powerUsageFeatureProvider,
132             MetricsFeatureProvider metricsFeatureProvider, Bundle bundle) {
133         // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|}
134         final StatsDimensionsValue intentDimsValue =
135                 bundle.getParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE);
136         final long timeMs = bundle.getLong(AnomalyDetectionReceiver.KEY_ANOMALY_TIMESTAMP,
137                 System.currentTimeMillis());
138         final ArrayList<String> cookies = bundle.getStringArrayList(
139                 StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES);
140         final AnomalyInfo anomalyInfo = new AnomalyInfo(
141                 !ArrayUtils.isEmpty(cookies) ? cookies.get(0) : "");
142         Log.i(TAG, "Extra stats value: " + intentDimsValue.toString());
143 
144         try {
145             final int uid = extractUidFromStatsDimensionsValue(intentDimsValue);
146             final boolean autoFeatureOn = powerUsageFeatureProvider.isSmartBatterySupported()
147                     ? Settings.Global.getInt(contentResolver,
148                             Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, ON) == ON
149                     : Settings.Global.getInt(contentResolver,
150                             Settings.Global.APP_AUTO_RESTRICTION_ENABLED, ON) == ON;
151             final String packageName = batteryUtils.getPackageName(uid);
152             final long versionCode = batteryUtils.getAppLongVersionCode(packageName);
153 
154             if (batteryUtils.shouldHideAnomaly(powerWhitelistBackend, uid, anomalyInfo)) {
155                 metricsFeatureProvider.action(context,
156                         MetricsProto.MetricsEvent.ACTION_ANOMALY_IGNORED,
157                         packageName,
158                         Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT,
159                                 anomalyInfo.anomalyType),
160                         Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
161                                 versionCode));
162             } else {
163                 if (autoFeatureOn && anomalyInfo.autoRestriction) {
164                     // Auto restrict this app
165                     batteryUtils.setForceAppStandby(uid, packageName,
166                             AppOpsManager.MODE_IGNORED);
167                     databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType,
168                             AnomalyDatabaseHelper.State.AUTO_HANDLED,
169                             timeMs);
170                 } else {
171                     databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType,
172                             AnomalyDatabaseHelper.State.NEW,
173                             timeMs);
174                 }
175                 metricsFeatureProvider.action(context,
176                         MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED,
177                         packageName,
178                         Pair.create(MetricsProto.MetricsEvent.FIELD_ANOMALY_TYPE,
179                                 anomalyInfo.anomalyType),
180                         Pair.create(MetricsProto.MetricsEvent.FIELD_APP_VERSION_CODE,
181                                 versionCode));
182             }
183 
184         } catch (NullPointerException | IndexOutOfBoundsException e) {
185             Log.e(TAG, "Parse stats dimensions value error.", e);
186         }
187     }
188 
189     /**
190      * Extract the uid from {@link StatsDimensionsValue}
191      *
192      * The uid dimension has the format: 1:<int> inside the tuple list. Here are some examples:
193      * 1. Excessive bg anomaly: 27:{1:10089|}
194      * 2. Wakeup alarm anomaly: 35:{1:{1:{1:10013|}|}|}
195      * 3. Bluetooth anomaly:    3:{1:{1:{1:10140|}|}|}
196      */
197     @VisibleForTesting
extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue)198     int extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue) {
199         if (statsDimensionsValue == null) {
200             return UID_NULL;
201         }
202         if (statsDimensionsValue.isValueType(INT_VALUE_TYPE)
203                 && statsDimensionsValue.getField() == STATSD_UID_FILED) {
204             // Find out the real uid
205             return statsDimensionsValue.getIntValue();
206         }
207         if (statsDimensionsValue.isValueType(TUPLE_VALUE_TYPE)) {
208             final List<StatsDimensionsValue> values = statsDimensionsValue.getTupleValueList();
209             for (int i = 0, size = values.size(); i < size; i++) {
210                 int uid = extractUidFromStatsDimensionsValue(values.get(i));
211                 if (uid != UID_NULL) {
212                     return uid;
213                 }
214             }
215         }
216 
217         return UID_NULL;
218     }
219 
220     @VisibleForTesting
dequeueWork(JobParameters parameters)221     JobWorkItem dequeueWork(JobParameters parameters) {
222         synchronized (mLock) {
223             if (mIsJobCanceled) {
224                 return null;
225             }
226 
227             return parameters.dequeueWork();
228         }
229     }
230 
231     @VisibleForTesting
completeWork(JobParameters parameters, JobWorkItem item)232     void completeWork(JobParameters parameters, JobWorkItem item) {
233         synchronized (mLock) {
234             if (mIsJobCanceled) {
235                 return;
236             }
237 
238             parameters.completeWork(item);
239         }
240     }
241 }
242