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