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.app.settings.SettingsEnums; 30 import android.content.ComponentName; 31 import android.content.ContentResolver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.os.Bundle; 35 import android.os.StatsDimensionsValue; 36 import android.os.UserManager; 37 import android.provider.Settings; 38 import android.util.Log; 39 40 import androidx.annotation.GuardedBy; 41 import androidx.annotation.VisibleForTesting; 42 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.PowerAllowlistBackend; 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 PowerAllowlistBackend powerAllowlistBackend = 100 PowerAllowlistBackend.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, powerAllowlistBackend, 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, PowerAllowlistBackend powerAllowlistBackend, ContentResolver contentResolver, PowerUsageFeatureProvider powerUsageFeatureProvider, MetricsFeatureProvider metricsFeatureProvider, Bundle bundle)128 void saveAnomalyToDatabase(Context context, UserManager userManager, 129 BatteryDatabaseManager databaseManager, BatteryUtils batteryUtils, 130 BatteryTipPolicy policy, PowerAllowlistBackend powerAllowlistBackend, 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 final String versionedPackage = packageName + "/" + versionCode; 154 if (batteryUtils.shouldHideAnomaly(powerAllowlistBackend, uid, anomalyInfo)) { 155 metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, 156 SettingsEnums.ACTION_ANOMALY_IGNORED, 157 SettingsEnums.PAGE_UNKNOWN, 158 versionedPackage, 159 anomalyInfo.anomalyType); 160 } else { 161 if (autoFeatureOn && anomalyInfo.autoRestriction) { 162 // Auto restrict this app 163 batteryUtils.setForceAppStandby(uid, packageName, 164 AppOpsManager.MODE_IGNORED); 165 databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType, 166 AnomalyDatabaseHelper.State.AUTO_HANDLED, 167 timeMs); 168 } else { 169 databaseManager.insertAnomaly(uid, packageName, anomalyInfo.anomalyType, 170 AnomalyDatabaseHelper.State.NEW, 171 timeMs); 172 } 173 metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, 174 SettingsEnums.ACTION_ANOMALY_TRIGGERED, 175 SettingsEnums.PAGE_UNKNOWN, 176 versionedPackage, 177 anomalyInfo.anomalyType); 178 } 179 180 } catch (NullPointerException | IndexOutOfBoundsException e) { 181 Log.e(TAG, "Parse stats dimensions value error.", e); 182 } 183 } 184 185 /** 186 * Extract the uid from {@link StatsDimensionsValue} 187 * 188 * The uid dimension has the format: 1:<int> inside the tuple list. Here are some examples: 189 * 1. Excessive bg anomaly: 27:{1:10089|} 190 * 2. Wakeup alarm anomaly: 35:{1:{1:{1:10013|}|}|} 191 * 3. Bluetooth anomaly: 3:{1:{1:{1:10140|}|}|} 192 */ 193 @VisibleForTesting extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue)194 int extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue) { 195 if (statsDimensionsValue == null) { 196 return UID_NULL; 197 } 198 if (statsDimensionsValue.isValueType(INT_VALUE_TYPE) 199 && statsDimensionsValue.getField() == STATSD_UID_FILED) { 200 // Find out the real uid 201 return statsDimensionsValue.getIntValue(); 202 } 203 if (statsDimensionsValue.isValueType(TUPLE_VALUE_TYPE)) { 204 final List<StatsDimensionsValue> values = statsDimensionsValue.getTupleValueList(); 205 for (int i = 0, size = values.size(); i < size; i++) { 206 int uid = extractUidFromStatsDimensionsValue(values.get(i)); 207 if (uid != UID_NULL) { 208 return uid; 209 } 210 } 211 } 212 213 return UID_NULL; 214 } 215 216 @VisibleForTesting dequeueWork(JobParameters parameters)217 JobWorkItem dequeueWork(JobParameters parameters) { 218 synchronized (mLock) { 219 if (mIsJobCanceled) { 220 return null; 221 } 222 223 return parameters.dequeueWork(); 224 } 225 } 226 227 @VisibleForTesting completeWork(JobParameters parameters, JobWorkItem item)228 void completeWork(JobParameters parameters, JobWorkItem item) { 229 synchronized (mLock) { 230 if (mIsJobCanceled) { 231 return; 232 } 233 234 parameters.completeWork(item); 235 } 236 } 237 } 238