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