Home | History | Annotate | Download | only in batterytip
      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 
     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
     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
    120     public boolean onStopJob(JobParameters jobParameters) {
    121         synchronized (mLock) {
    122             mIsJobCanceled = true;
    123         }
    124         return true; // Need to reschedule
    125     }
    126 
    127     @VisibleForTesting
    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
    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
    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
    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