1 /* 2 * Copyright (C) 2017 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; 18 19 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUserConsumer; 20 21 import android.app.Activity; 22 import android.app.ActivityManager; 23 import android.app.settings.SettingsEnums; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 import android.util.Log; 30 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.settings.R; 34 import com.android.settings.SettingsActivity; 35 import com.android.settings.applications.appinfo.AppButtonsPreferenceController; 36 import com.android.settings.applications.appinfo.ButtonActionDialogFragment; 37 import com.android.settings.core.InstrumentedPreferenceFragment; 38 import com.android.settings.core.SubSettingLauncher; 39 import com.android.settings.dashboard.DashboardFragment; 40 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; 41 import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; 42 import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry; 43 import com.android.settings.fuelgauge.batteryusage.BatteryEntry; 44 import com.android.settings.overlay.FeatureFactory; 45 import com.android.settingslib.Utils; 46 import com.android.settingslib.applications.ApplicationsState; 47 import com.android.settingslib.core.AbstractPreferenceController; 48 import com.android.settingslib.core.instrumentation.Instrumentable; 49 import com.android.settingslib.widget.IntroPreference; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.concurrent.ExecutorService; 54 import java.util.concurrent.Executors; 55 56 /** 57 * Power usage detail fragment for each app, this fragment contains <br> 58 * <br> 59 * 1. Detail battery usage information for app(i.e. usage time, usage amount) <br> 60 * 2. Battery related controls for app(i.e uninstall, force stop) 61 */ 62 public class AdvancedPowerUsageDetail extends DashboardFragment 63 implements ButtonActionDialogFragment.AppButtonsDialogListener { 64 public static final String TAG = "AdvancedPowerDetail"; 65 public static final String EXTRA_UID = "extra_uid"; 66 public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; 67 public static final String EXTRA_FOREGROUND_TIME = "extra_foreground_time"; 68 public static final String EXTRA_BACKGROUND_TIME = "extra_background_time"; 69 public static final String EXTRA_SCREEN_ON_TIME = "extra_screen_on_time"; 70 public static final String EXTRA_ANOMALY_HINT_PREF_KEY = "extra_anomaly_hint_pref_key"; 71 public static final String EXTRA_ANOMALY_HINT_TEXT = "extra_anomaly_hint_text"; 72 public static final String EXTRA_SHOW_TIME_INFO = "extra_show_time_info"; 73 public static final String EXTRA_SLOT_TIME = "extra_slot_time"; 74 public static final String EXTRA_LABEL = "extra_label"; 75 public static final String EXTRA_ICON_ID = "extra_icon_id"; 76 public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent"; 77 public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount"; 78 79 private static final String KEY_PREF_HEADER = "header_view"; 80 private static final String KEY_BACKGROUND_USAGE_ALLOWABILITY_CATEGORY = 81 "background_usage_allowability_category"; 82 83 private static final int REQUEST_UNINSTALL = 0; 84 private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; 85 86 private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); 87 88 private AppButtonsPreferenceController mAppButtonsPreferenceController; 89 private PowerUsageTimeController mPowerUsageTimeController; 90 91 @VisibleForTesting ApplicationsState mState; 92 @VisibleForTesting ApplicationsState.AppEntry mAppEntry; 93 @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils; 94 95 @VisibleForTesting @BatteryOptimizeUtils.OptimizationMode 96 int mOptimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN; 97 98 @VisibleForTesting StringBuilder mLogStringBuilder; 99 100 // A wrapper class to carry LaunchBatteryDetailPage required arguments. 101 private static final class LaunchBatteryDetailPageArgs { 102 private String mUsagePercent; 103 private String mPackageName; 104 private String mAppLabel; 105 private String mSlotInformation; 106 private String mAnomalyHintText; 107 private String mAnomalyHintPrefKey; 108 private int mUid; 109 private int mIconId; 110 private int mConsumedPower; 111 private long mForegroundTimeMs; 112 private long mBackgroundTimeMs; 113 private long mScreenOnTimeMs; 114 private boolean mShowTimeInformation; 115 private boolean mIsUserEntry; 116 } 117 118 /** Launches battery details page for an individual battery consumer fragment. */ startBatteryDetailPage( Context context, int sourceMetricsCategory, BatteryDiffEntry diffEntry, String usagePercent, String slotInformation, boolean showTimeInformation, String anomalyHintPrefKey, String anomalyHintText)119 public static void startBatteryDetailPage( 120 Context context, 121 int sourceMetricsCategory, 122 BatteryDiffEntry diffEntry, 123 String usagePercent, 124 String slotInformation, 125 boolean showTimeInformation, 126 String anomalyHintPrefKey, 127 String anomalyHintText) { 128 final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs(); 129 // configure the launch argument. 130 launchArgs.mUsagePercent = usagePercent; 131 launchArgs.mPackageName = diffEntry.getPackageName(); 132 launchArgs.mAppLabel = diffEntry.getAppLabel(); 133 launchArgs.mSlotInformation = slotInformation; 134 launchArgs.mUid = (int) diffEntry.mUid; 135 launchArgs.mIconId = diffEntry.getAppIconId(); 136 launchArgs.mConsumedPower = (int) diffEntry.mConsumePower; 137 launchArgs.mShowTimeInformation = showTimeInformation; 138 if (launchArgs.mShowTimeInformation) { 139 launchArgs.mForegroundTimeMs = diffEntry.mForegroundUsageTimeInMs; 140 launchArgs.mBackgroundTimeMs = 141 diffEntry.mBackgroundUsageTimeInMs + diffEntry.mForegroundServiceUsageTimeInMs; 142 launchArgs.mScreenOnTimeMs = diffEntry.mScreenOnTimeInMs; 143 launchArgs.mAnomalyHintPrefKey = anomalyHintPrefKey; 144 launchArgs.mAnomalyHintText = anomalyHintText; 145 } 146 launchArgs.mIsUserEntry = isUserConsumer(diffEntry.mConsumerType); 147 startBatteryDetailPage(context, sourceMetricsCategory, launchArgs); 148 } 149 150 /** Launches battery details page for an individual battery consumer. */ startBatteryDetailPage( Activity caller, InstrumentedPreferenceFragment fragment, BatteryEntry entry, String usagePercent)151 public static void startBatteryDetailPage( 152 Activity caller, 153 InstrumentedPreferenceFragment fragment, 154 BatteryEntry entry, 155 String usagePercent) { 156 final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs(); 157 // configure the launch argument. 158 launchArgs.mUsagePercent = usagePercent; 159 launchArgs.mPackageName = entry.getDefaultPackageName(); 160 launchArgs.mAppLabel = entry.getLabel(); 161 launchArgs.mUid = entry.getUid(); 162 launchArgs.mIconId = entry.mIconId; 163 launchArgs.mConsumedPower = (int) entry.getConsumedPower(); 164 launchArgs.mIsUserEntry = entry.isUserEntry(); 165 launchArgs.mShowTimeInformation = false; 166 startBatteryDetailPage(caller, fragment.getMetricsCategory(), launchArgs); 167 } 168 startBatteryDetailPage( Context context, int sourceMetricsCategory, LaunchBatteryDetailPageArgs launchArgs)169 private static void startBatteryDetailPage( 170 Context context, int sourceMetricsCategory, LaunchBatteryDetailPageArgs launchArgs) { 171 final Bundle args = new Bundle(); 172 if (launchArgs.mPackageName == null) { 173 // populate data for system app 174 args.putString(EXTRA_LABEL, launchArgs.mAppLabel); 175 args.putInt(EXTRA_ICON_ID, launchArgs.mIconId); 176 args.putString(EXTRA_PACKAGE_NAME, null); 177 } else { 178 // populate data for normal app 179 args.putString(EXTRA_PACKAGE_NAME, launchArgs.mPackageName); 180 } 181 182 args.putInt(EXTRA_UID, launchArgs.mUid); 183 args.putLong(EXTRA_BACKGROUND_TIME, launchArgs.mBackgroundTimeMs); 184 args.putLong(EXTRA_FOREGROUND_TIME, launchArgs.mForegroundTimeMs); 185 args.putLong(EXTRA_SCREEN_ON_TIME, launchArgs.mScreenOnTimeMs); 186 args.putString(EXTRA_SLOT_TIME, launchArgs.mSlotInformation); 187 args.putString(EXTRA_POWER_USAGE_PERCENT, launchArgs.mUsagePercent); 188 args.putInt(EXTRA_POWER_USAGE_AMOUNT, launchArgs.mConsumedPower); 189 args.putBoolean(EXTRA_SHOW_TIME_INFO, launchArgs.mShowTimeInformation); 190 args.putString(EXTRA_ANOMALY_HINT_PREF_KEY, launchArgs.mAnomalyHintPrefKey); 191 args.putString(EXTRA_ANOMALY_HINT_TEXT, launchArgs.mAnomalyHintText); 192 final int userId = 193 launchArgs.mIsUserEntry 194 ? ActivityManager.getCurrentUser() 195 : UserHandle.getUserId(launchArgs.mUid); 196 197 new SubSettingLauncher(context) 198 .setDestination(AdvancedPowerUsageDetail.class.getName()) 199 .setTitleRes(R.string.battery_details_title) 200 .setArguments(args) 201 .setSourceMetricsCategory(sourceMetricsCategory) 202 .setUserHandle(new UserHandle(userId)) 203 .launch(); 204 } 205 206 /** Start packageName's battery detail page. */ startBatteryDetailPage( Activity caller, Instrumentable instrumentable, String packageName, UserHandle userHandle)207 public static void startBatteryDetailPage( 208 Activity caller, 209 Instrumentable instrumentable, 210 String packageName, 211 UserHandle userHandle) { 212 final Bundle args = new Bundle(3); 213 final PackageManager packageManager = caller.getPackageManager(); 214 args.putString(EXTRA_PACKAGE_NAME, packageName); 215 args.putString(EXTRA_POWER_USAGE_PERCENT, Utils.formatPercentage(0)); 216 try { 217 args.putInt(EXTRA_UID, packageManager.getPackageUid(packageName, 0 /* no flag */)); 218 } catch (PackageManager.NameNotFoundException e) { 219 Log.w(TAG, "Cannot find package: " + packageName, e); 220 } 221 222 new SubSettingLauncher(caller) 223 .setDestination(AdvancedPowerUsageDetail.class.getName()) 224 .setTitleRes(R.string.battery_details_title) 225 .setArguments(args) 226 .setSourceMetricsCategory(instrumentable.getMetricsCategory()) 227 .setUserHandle(userHandle) 228 .launch(); 229 } 230 231 @Override onAttach(Activity activity)232 public void onAttach(Activity activity) { 233 super.onAttach(activity); 234 235 final Bundle bundle = getArguments(); 236 final int uid = bundle.getInt(EXTRA_UID, 0); 237 final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); 238 mBatteryOptimizeUtils = new BatteryOptimizeUtils(getContext(), uid, packageName); 239 mState = ApplicationsState.getInstance(getActivity().getApplication()); 240 if (packageName != null) { 241 mAppEntry = mState.getEntry(packageName, UserHandle.myUserId()); 242 } 243 } 244 245 @Override onResume()246 public void onResume() { 247 super.onResume(); 248 249 initHeader(); 250 mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode(); 251 mLogStringBuilder = new StringBuilder("onResume mode = ").append(mOptimizationMode); 252 } 253 254 @Override shouldSkipForInitialSUW()255 protected boolean shouldSkipForInitialSUW() { 256 return true; 257 } 258 259 @Override onPause()260 public void onPause() { 261 super.onPause(); 262 263 final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode(); 264 final Context applicationContext = requireContext().getApplicationContext(); 265 mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode); 266 logMetricCategory(currentOptimizeMode); 267 mExecutor.execute( 268 () -> { 269 if (currentOptimizeMode != mOptimizationMode) { 270 AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid( 271 applicationContext, mBatteryOptimizeUtils.getUid()); 272 } 273 BatteryOptimizeLogUtils.writeLog( 274 applicationContext, 275 Action.LEAVE, 276 BatteryOptimizeLogUtils.getPackageNameWithUserId( 277 mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()), 278 mLogStringBuilder.toString()); 279 }); 280 Log.d(TAG, "Leave with mode: " + currentOptimizeMode); 281 } 282 283 @VisibleForTesting initHeader()284 void initHeader() { 285 final IntroPreference introPreference = findPreference(KEY_PREF_HEADER); 286 if (introPreference == null) { 287 return; 288 } 289 final Activity context = getActivity(); 290 final Bundle bundle = getArguments(); 291 292 if (mAppEntry == null) { 293 introPreference.setTitle(bundle.getString(EXTRA_LABEL)); 294 295 final int iconId = bundle.getInt(EXTRA_ICON_ID, 0); 296 if (iconId == 0) { 297 introPreference.setIcon(context.getPackageManager().getDefaultActivityIcon()); 298 } else { 299 introPreference.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID))); 300 } 301 } else { 302 mState.ensureIcon(mAppEntry); 303 introPreference.setTitle(mAppEntry.label); 304 introPreference.setIcon(Utils.getBadgedIcon(context, mAppEntry.info)); 305 } 306 307 if (mPowerUsageTimeController != null) { 308 final String slotTime = bundle.getString(EXTRA_SLOT_TIME); 309 final long screenOnTimeInMs = bundle.getLong(EXTRA_SCREEN_ON_TIME); 310 final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME); 311 final String anomalyHintPrefKey = bundle.getString(EXTRA_ANOMALY_HINT_PREF_KEY); 312 final String anomalyHintText = bundle.getString(EXTRA_ANOMALY_HINT_TEXT); 313 mPowerUsageTimeController.handleScreenTimeUpdated( 314 slotTime, 315 screenOnTimeInMs, 316 backgroundTimeMs, 317 anomalyHintPrefKey, 318 anomalyHintText); 319 } 320 } 321 322 @Override getMetricsCategory()323 public int getMetricsCategory() { 324 return SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL; 325 } 326 327 @Override getLogTag()328 protected String getLogTag() { 329 return TAG; 330 } 331 332 @Override getPreferenceScreenResId()333 protected int getPreferenceScreenResId() { 334 return R.xml.power_usage_detail; 335 } 336 337 @Override createPreferenceControllers(Context context)338 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 339 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 340 final Bundle bundle = getArguments(); 341 final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); 342 343 mAppButtonsPreferenceController = 344 new AppButtonsPreferenceController( 345 (SettingsActivity) getActivity(), 346 this, 347 getSettingsLifecycle(), 348 packageName, 349 mState, 350 REQUEST_UNINSTALL, 351 REQUEST_REMOVE_DEVICE_ADMIN); 352 if (bundle.getBoolean(EXTRA_SHOW_TIME_INFO, false)) { 353 mPowerUsageTimeController = new PowerUsageTimeController(getContext()); 354 controllers.add(mPowerUsageTimeController); 355 } 356 controllers.add(mAppButtonsPreferenceController); 357 controllers.add( 358 new BackgroundUsageAllowabilityPreferenceController( 359 context, 360 /* dashboardFragment= */ this, 361 KEY_BACKGROUND_USAGE_ALLOWABILITY_CATEGORY, 362 mBatteryOptimizeUtils)); 363 364 return controllers; 365 } 366 367 @Override onActivityResult(int requestCode, int resultCode, Intent data)368 public void onActivityResult(int requestCode, int resultCode, Intent data) { 369 super.onActivityResult(requestCode, resultCode, data); 370 if (mAppButtonsPreferenceController != null) { 371 mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data); 372 } 373 } 374 375 @Override handleDialogClick(int id)376 public void handleDialogClick(int id) { 377 if (mAppButtonsPreferenceController != null) { 378 mAppButtonsPreferenceController.handleDialogClick(id); 379 } 380 } 381 logMetricCategory(int currentOptimizeMode)382 private void logMetricCategory(int currentOptimizeMode) { 383 if (currentOptimizeMode == mOptimizationMode) { 384 return; 385 } 386 int metricCategory = 0; 387 switch (currentOptimizeMode) { 388 case BatteryOptimizeUtils.MODE_UNRESTRICTED: 389 case BatteryOptimizeUtils.MODE_OPTIMIZED: 390 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_ALLOW_BACKGROUND; 391 break; 392 case BatteryOptimizeUtils.MODE_RESTRICTED: 393 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_DISABLE_BACKGROUND; 394 break; 395 } 396 if (metricCategory == 0) { 397 return; 398 } 399 int finalMetricCategory = metricCategory; 400 mExecutor.execute( 401 () -> { 402 String packageName = 403 BatteryUtils.getLoggingPackageName( 404 getContext(), mBatteryOptimizeUtils.getPackageName()); 405 FeatureFactory.getFeatureFactory() 406 .getMetricsFeatureProvider() 407 .action( 408 /* attribution */ SettingsEnums.LEAVE_APP_BATTERY_USAGE, 409 /* action */ finalMetricCategory, 410 /* pageId */ SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL, 411 packageName, 412 getArguments().getInt(EXTRA_POWER_USAGE_AMOUNT)); 413 }); 414 } 415 } 416