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 shouldSkipForInitialSUW()272 protected boolean shouldSkipForInitialSUW() { 273 return true; 274 } 275 276 @Override onPause()277 public void onPause() { 278 super.onPause(); 279 280 final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode(); 281 mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode); 282 logMetricCategory(currentOptimizeMode); 283 mExecutor.execute( 284 () -> { 285 if (currentOptimizeMode != mOptimizationMode) { 286 AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid( 287 getContext(), mBatteryOptimizeUtils.getUid()); 288 } 289 BatteryOptimizeLogUtils.writeLog( 290 getContext().getApplicationContext(), 291 Action.LEAVE, 292 BatteryOptimizeLogUtils.getPackageNameWithUserId( 293 mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()), 294 mLogStringBuilder.toString()); 295 }); 296 Log.d(TAG, "Leave with mode: " + currentOptimizeMode); 297 } 298 299 @VisibleForTesting initHeader()300 void initHeader() { 301 final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header); 302 final Activity context = getActivity(); 303 final Bundle bundle = getArguments(); 304 EntityHeaderController controller = 305 EntityHeaderController.newInstance(context, this, appSnippet) 306 .setButtonActions( 307 EntityHeaderController.ActionType.ACTION_NONE, 308 EntityHeaderController.ActionType.ACTION_NONE); 309 310 if (mAppEntry == null) { 311 controller.setLabel(bundle.getString(EXTRA_LABEL)); 312 313 final int iconId = bundle.getInt(EXTRA_ICON_ID, 0); 314 if (iconId == 0) { 315 controller.setIcon(context.getPackageManager().getDefaultActivityIcon()); 316 } else { 317 controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID))); 318 } 319 } else { 320 mState.ensureIcon(mAppEntry); 321 controller.setLabel(mAppEntry); 322 controller.setIcon(mAppEntry); 323 controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info)); 324 } 325 326 if (mPowerUsageTimeController != null) { 327 final String slotTime = bundle.getString(EXTRA_SLOT_TIME); 328 final long screenOnTimeInMs = bundle.getLong(EXTRA_SCREEN_ON_TIME); 329 final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME); 330 final String anomalyHintPrefKey = bundle.getString(EXTRA_ANOMALY_HINT_PREF_KEY); 331 final String anomalyHintText = bundle.getString(EXTRA_ANOMALY_HINT_TEXT); 332 mPowerUsageTimeController.handleScreenTimeUpdated( 333 slotTime, 334 screenOnTimeInMs, 335 backgroundTimeMs, 336 anomalyHintPrefKey, 337 anomalyHintText); 338 } 339 controller.done(true /* rebindActions */); 340 } 341 342 @VisibleForTesting initFooter()343 void initFooter() { 344 final String stateString; 345 final String detailInfoString; 346 final Context context = getContext(); 347 348 if (mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()) { 349 // Present optimized only string when the package name is invalid. 350 stateString = context.getString(R.string.manager_battery_usage_optimized_only); 351 detailInfoString = 352 context.getString(R.string.manager_battery_usage_footer_limited, stateString); 353 } else if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) { 354 // Present unrestricted only string when the package is system or default active app. 355 stateString = context.getString(R.string.manager_battery_usage_unrestricted_only); 356 detailInfoString = 357 context.getString(R.string.manager_battery_usage_footer_limited, stateString); 358 } else { 359 // Present default string to normal app. 360 detailInfoString = 361 context.getString( 362 R.string.manager_battery_usage_allow_background_usage_summary); 363 } 364 mAllowBackgroundUsagePreference.setSummary(detailInfoString); 365 } 366 367 @Override getMetricsCategory()368 public int getMetricsCategory() { 369 return SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL; 370 } 371 372 @Override getLogTag()373 protected String getLogTag() { 374 return TAG; 375 } 376 377 @Override getPreferenceScreenResId()378 protected int getPreferenceScreenResId() { 379 return R.xml.power_usage_detail; 380 } 381 382 @Override createPreferenceControllers(Context context)383 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 384 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 385 final Bundle bundle = getArguments(); 386 final int uid = bundle.getInt(EXTRA_UID, 0); 387 final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); 388 389 mAppButtonsPreferenceController = 390 new AppButtonsPreferenceController( 391 (SettingsActivity) getActivity(), 392 this, 393 getSettingsLifecycle(), 394 packageName, 395 mState, 396 REQUEST_UNINSTALL, 397 REQUEST_REMOVE_DEVICE_ADMIN); 398 if (bundle.getBoolean(EXTRA_SHOW_TIME_INFO, false)) { 399 mPowerUsageTimeController = new PowerUsageTimeController(getContext()); 400 controllers.add(mPowerUsageTimeController); 401 } 402 controllers.add(mAppButtonsPreferenceController); 403 controllers.add(new AllowBackgroundPreferenceController(context, uid, packageName)); 404 405 return controllers; 406 } 407 408 @Override onActivityResult(int requestCode, int resultCode, Intent data)409 public void onActivityResult(int requestCode, int resultCode, Intent data) { 410 super.onActivityResult(requestCode, resultCode, data); 411 if (mAppButtonsPreferenceController != null) { 412 mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data); 413 } 414 } 415 416 @Override handleDialogClick(int id)417 public void handleDialogClick(int id) { 418 if (mAppButtonsPreferenceController != null) { 419 mAppButtonsPreferenceController.handleDialogClick(id); 420 } 421 } 422 423 @Override onPreferenceClick(Preference preference)424 public boolean onPreferenceClick(Preference preference) { 425 if (!(preference instanceof PrimarySwitchPreference) 426 || !TextUtils.equals(preference.getKey(), KEY_ALLOW_BACKGROUND_USAGE)) { 427 return false; 428 } 429 PowerBackgroundUsageDetail.startPowerBackgroundUsageDetailPage( 430 getContext(), getArguments()); 431 return true; 432 } 433 434 @Override onPreferenceChange(@onNull Preference preference, Object newValue)435 public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { 436 if (!(preference instanceof PrimarySwitchPreference) 437 || !TextUtils.equals(preference.getKey(), KEY_ALLOW_BACKGROUND_USAGE)) { 438 return false; 439 } 440 if (newValue instanceof Boolean) { 441 final boolean isAllowBackgroundUsage = (boolean) newValue; 442 mBatteryOptimizeUtils.setAppUsageState( 443 isAllowBackgroundUsage 444 ? BatteryOptimizeUtils.MODE_OPTIMIZED 445 : BatteryOptimizeUtils.MODE_RESTRICTED, 446 Action.APPLY); 447 } 448 return true; 449 } 450 logMetricCategory(int currentOptimizeMode)451 private void logMetricCategory(int currentOptimizeMode) { 452 if (currentOptimizeMode == mOptimizationMode) { 453 return; 454 } 455 int metricCategory = 0; 456 switch (currentOptimizeMode) { 457 case BatteryOptimizeUtils.MODE_UNRESTRICTED: 458 case BatteryOptimizeUtils.MODE_OPTIMIZED: 459 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_ALLOW_BACKGROUND; 460 break; 461 case BatteryOptimizeUtils.MODE_RESTRICTED: 462 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_DISABLE_BACKGROUND; 463 break; 464 } 465 if (metricCategory == 0) { 466 return; 467 } 468 int finalMetricCategory = metricCategory; 469 mExecutor.execute( 470 () -> { 471 String packageName = 472 BatteryUtils.getLoggingPackageName( 473 getContext(), mBatteryOptimizeUtils.getPackageName()); 474 FeatureFactory.getFeatureFactory() 475 .getMetricsFeatureProvider() 476 .action( 477 /* attribution */ SettingsEnums.LEAVE_APP_BATTERY_USAGE, 478 /* action */ finalMetricCategory, 479 /* pageId */ SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL, 480 packageName, 481 getArguments().getInt(EXTRA_POWER_USAGE_AMOUNT)); 482 }); 483 } 484 onCreateBackgroundUsageState(String packageName)485 private void onCreateBackgroundUsageState(String packageName) { 486 mAllowBackgroundUsagePreference = findPreference(KEY_ALLOW_BACKGROUND_USAGE); 487 if (mAllowBackgroundUsagePreference != null) { 488 mAllowBackgroundUsagePreference.setOnPreferenceClickListener(this); 489 mAllowBackgroundUsagePreference.setOnPreferenceChangeListener(this); 490 } 491 492 mBatteryOptimizeUtils = 493 new BatteryOptimizeUtils( 494 getContext(), getArguments().getInt(EXTRA_UID), packageName); 495 } 496 } 497