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 android.annotation.UserIdInt; 20 import android.app.Activity; 21 import android.app.ActivityManager; 22 import android.app.LoaderManager; 23 import android.app.admin.DevicePolicyManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.Loader; 27 import android.content.pm.PackageManager; 28 import android.os.BatteryStats; 29 import android.os.Bundle; 30 import android.os.UserHandle; 31 import android.os.UserManager; 32 import android.support.annotation.VisibleForTesting; 33 import android.support.v7.preference.Preference; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.View; 37 38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 39 import com.android.internal.os.BatterySipper; 40 import com.android.internal.os.BatteryStatsHelper; 41 import com.android.internal.util.ArrayUtils; 42 import com.android.settings.R; 43 import com.android.settings.SettingsActivity; 44 import com.android.settings.Utils; 45 import com.android.settings.applications.LayoutPreference; 46 import com.android.settings.core.InstrumentedPreferenceFragment; 47 import com.android.settings.core.SubSettingLauncher; 48 import com.android.settings.dashboard.DashboardFragment; 49 import com.android.settings.fuelgauge.anomaly.Anomaly; 50 import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment; 51 import com.android.settings.fuelgauge.anomaly.AnomalyLoader; 52 import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController; 53 import com.android.settings.fuelgauge.anomaly.AnomalyUtils; 54 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; 55 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; 56 import com.android.settings.widget.EntityHeaderController; 57 import com.android.settingslib.applications.AppUtils; 58 import com.android.settingslib.applications.ApplicationsState; 59 import com.android.settingslib.core.AbstractPreferenceController; 60 import com.android.settingslib.utils.StringUtil; 61 62 import java.util.ArrayList; 63 import java.util.List; 64 65 /** 66 * Power usage detail fragment for each app, this fragment contains 67 * 68 * 1. Detail battery usage information for app(i.e. usage time, usage amount) 69 * 2. Battery related controls for app(i.e uninstall, force stop) 70 */ 71 public class AdvancedPowerUsageDetail extends DashboardFragment implements 72 ButtonActionDialogFragment.AppButtonsDialogListener, 73 AnomalyDialogFragment.AnomalyDialogListener, 74 LoaderManager.LoaderCallbacks<List<Anomaly>>, 75 BatteryTipPreferenceController.BatteryTipListener { 76 77 public static final String TAG = "AdvancedPowerDetail"; 78 public static final String EXTRA_UID = "extra_uid"; 79 public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; 80 public static final String EXTRA_FOREGROUND_TIME = "extra_foreground_time"; 81 public static final String EXTRA_BACKGROUND_TIME = "extra_background_time"; 82 public static final String EXTRA_LABEL = "extra_label"; 83 public static final String EXTRA_ICON_ID = "extra_icon_id"; 84 public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent"; 85 public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount"; 86 public static final String EXTRA_ANOMALY_LIST = "extra_anomaly_list"; 87 88 private static final String KEY_PREF_FOREGROUND = "app_usage_foreground"; 89 private static final String KEY_PREF_BACKGROUND = "app_usage_background"; 90 private static final String KEY_PREF_HEADER = "header_view"; 91 92 private static final int REQUEST_UNINSTALL = 0; 93 private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; 94 95 private static final int ANOMALY_LOADER = 0; 96 97 @VisibleForTesting 98 LayoutPreference mHeaderPreference; 99 @VisibleForTesting 100 ApplicationsState mState; 101 @VisibleForTesting 102 ApplicationsState.AppEntry mAppEntry; 103 @VisibleForTesting 104 BatteryUtils mBatteryUtils; 105 106 @VisibleForTesting 107 Preference mForegroundPreference; 108 @VisibleForTesting 109 Preference mBackgroundPreference; 110 @VisibleForTesting 111 AnomalySummaryPreferenceController mAnomalySummaryPreferenceController; 112 private AppButtonsPreferenceController mAppButtonsPreferenceController; 113 private BackgroundActivityPreferenceController mBackgroundActivityPreferenceController; 114 115 private DevicePolicyManager mDpm; 116 private UserManager mUserManager; 117 private PackageManager mPackageManager; 118 private List<Anomaly> mAnomalies; 119 private String mPackageName; 120 121 @VisibleForTesting 122 static void startBatteryDetailPage(Activity caller, BatteryUtils batteryUtils, 123 InstrumentedPreferenceFragment fragment, BatteryStatsHelper helper, int which, 124 BatteryEntry entry, String usagePercent, List<Anomaly> anomalies) { 125 // Initialize mStats if necessary. 126 helper.getStats(); 127 128 final Bundle args = new Bundle(); 129 final BatterySipper sipper = entry.sipper; 130 final BatteryStats.Uid uid = sipper.uidObj; 131 final boolean isTypeApp = sipper.drainType == BatterySipper.DrainType.APP; 132 133 final long foregroundTimeMs = isTypeApp ? batteryUtils.getProcessTimeMs( 134 BatteryUtils.StatusType.FOREGROUND, uid, which) : sipper.usageTimeMs; 135 final long backgroundTimeMs = isTypeApp ? batteryUtils.getProcessTimeMs( 136 BatteryUtils.StatusType.BACKGROUND, uid, which) : 0; 137 138 if (ArrayUtils.isEmpty(sipper.mPackages)) { 139 // populate data for system app 140 args.putString(EXTRA_LABEL, entry.getLabel()); 141 args.putInt(EXTRA_ICON_ID, entry.iconId); 142 args.putString(EXTRA_PACKAGE_NAME, null); 143 } else { 144 // populate data for normal app 145 args.putString(EXTRA_PACKAGE_NAME, entry.defaultPackageName != null 146 ? entry.defaultPackageName 147 : sipper.mPackages[0]); 148 } 149 150 args.putInt(EXTRA_UID, sipper.getUid()); 151 args.putLong(EXTRA_BACKGROUND_TIME, backgroundTimeMs); 152 args.putLong(EXTRA_FOREGROUND_TIME, foregroundTimeMs); 153 args.putString(EXTRA_POWER_USAGE_PERCENT, usagePercent); 154 args.putInt(EXTRA_POWER_USAGE_AMOUNT, (int) sipper.totalPowerMah); 155 args.putParcelableList(EXTRA_ANOMALY_LIST, anomalies); 156 157 new SubSettingLauncher(caller) 158 .setDestination(AdvancedPowerUsageDetail.class.getName()) 159 .setTitle(R.string.battery_details_title) 160 .setArguments(args) 161 .setSourceMetricsCategory(fragment.getMetricsCategory()) 162 .setUserHandle(new UserHandle(getUserIdToLaunchAdvancePowerUsageDetail(sipper))) 163 .launch(); 164 } 165 166 private static @UserIdInt 167 int getUserIdToLaunchAdvancePowerUsageDetail(BatterySipper bs) { 168 if (bs.drainType == BatterySipper.DrainType.USER) { 169 return ActivityManager.getCurrentUser(); 170 } 171 return UserHandle.getUserId(bs.getUid()); 172 } 173 174 public static void startBatteryDetailPage(Activity caller, 175 InstrumentedPreferenceFragment fragment, BatteryStatsHelper helper, int which, 176 BatteryEntry entry, String usagePercent, List<Anomaly> anomalies) { 177 startBatteryDetailPage(caller, BatteryUtils.getInstance(caller), fragment, helper, which, 178 entry, usagePercent, anomalies); 179 } 180 181 public static void startBatteryDetailPage(Activity caller, 182 InstrumentedPreferenceFragment fragment, String packageName) { 183 final Bundle args = new Bundle(3); 184 final PackageManager packageManager = caller.getPackageManager(); 185 args.putString(EXTRA_PACKAGE_NAME, packageName); 186 args.putString(EXTRA_POWER_USAGE_PERCENT, Utils.formatPercentage(0)); 187 try { 188 args.putInt(EXTRA_UID, packageManager.getPackageUid(packageName, 0 /* no flag */)); 189 } catch (PackageManager.NameNotFoundException e) { 190 Log.e(TAG, "Cannot find package: " + packageName, e); 191 } 192 193 new SubSettingLauncher(caller) 194 .setDestination(AdvancedPowerUsageDetail.class.getName()) 195 .setTitle(R.string.battery_details_title) 196 .setArguments(args) 197 .setSourceMetricsCategory(fragment.getMetricsCategory()) 198 .launch(); 199 } 200 201 @Override 202 public void onAttach(Activity activity) { 203 super.onAttach(activity); 204 205 mState = ApplicationsState.getInstance(getActivity().getApplication()); 206 mDpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE); 207 mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE); 208 mPackageManager = activity.getPackageManager(); 209 mBatteryUtils = BatteryUtils.getInstance(getContext()); 210 } 211 212 @Override 213 public void onCreate(Bundle icicle) { 214 super.onCreate(icicle); 215 216 mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME); 217 mAnomalySummaryPreferenceController = new AnomalySummaryPreferenceController( 218 (SettingsActivity) getActivity(), this); 219 mForegroundPreference = findPreference(KEY_PREF_FOREGROUND); 220 mBackgroundPreference = findPreference(KEY_PREF_BACKGROUND); 221 mHeaderPreference = (LayoutPreference) findPreference(KEY_PREF_HEADER); 222 223 if (mPackageName != null) { 224 mAppEntry = mState.getEntry(mPackageName, UserHandle.myUserId()); 225 initAnomalyInfo(); 226 } 227 } 228 229 @Override 230 public void onResume() { 231 super.onResume(); 232 233 initHeader(); 234 initPreference(); 235 } 236 237 @VisibleForTesting 238 void initAnomalyInfo() { 239 mAnomalies = getArguments().getParcelableArrayList(EXTRA_ANOMALY_LIST); 240 if (mAnomalies == null) { 241 getLoaderManager().initLoader(ANOMALY_LOADER, Bundle.EMPTY, this); 242 } else if (mAnomalies != null) { 243 mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(mAnomalies); 244 } 245 } 246 247 @VisibleForTesting 248 void initHeader() { 249 final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header); 250 final Activity context = getActivity(); 251 final Bundle bundle = getArguments(); 252 EntityHeaderController controller = EntityHeaderController 253 .newInstance(context, this, appSnippet) 254 .setRecyclerView(getListView(), getLifecycle()) 255 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, 256 EntityHeaderController.ActionType.ACTION_NONE); 257 258 if (mAppEntry == null) { 259 controller.setLabel(bundle.getString(EXTRA_LABEL)); 260 261 final int iconId = bundle.getInt(EXTRA_ICON_ID, 0); 262 if (iconId == 0) { 263 controller.setIcon(context.getPackageManager().getDefaultActivityIcon()); 264 } else { 265 controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID))); 266 } 267 } else { 268 mState.ensureIcon(mAppEntry); 269 controller.setLabel(mAppEntry); 270 controller.setIcon(mAppEntry); 271 boolean isInstantApp = AppUtils.isInstant(mAppEntry.info); 272 CharSequence summary = isInstantApp 273 ? null : getString(Utils.getInstallationStatus(mAppEntry.info)); 274 controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info)); 275 controller.setSummary(summary); 276 } 277 278 controller.done(context, true /* rebindActions */); 279 } 280 281 @VisibleForTesting 282 void initPreference() { 283 final Bundle bundle = getArguments(); 284 final Context context = getContext(); 285 286 final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME); 287 final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME); 288 final String usagePercent = bundle.getString(EXTRA_POWER_USAGE_PERCENT); 289 final int powerMah = bundle.getInt(EXTRA_POWER_USAGE_AMOUNT); 290 mForegroundPreference.setSummary( 291 TextUtils.expandTemplate(getText(R.string.battery_used_for), 292 StringUtil.formatElapsedTime(context, foregroundTimeMs, false))); 293 mBackgroundPreference.setSummary( 294 TextUtils.expandTemplate(getText(R.string.battery_active_for), 295 StringUtil.formatElapsedTime(context, backgroundTimeMs, false))); 296 } 297 298 @Override 299 public boolean onPreferenceTreeClick(Preference preference) { 300 if (TextUtils.equals(preference.getKey(), AnomalySummaryPreferenceController.ANOMALY_KEY)) { 301 mAnomalySummaryPreferenceController.onPreferenceTreeClick(preference); 302 return true; 303 } 304 return super.onPreferenceTreeClick(preference); 305 } 306 307 @Override 308 public int getMetricsCategory() { 309 return MetricsEvent.FUELGAUGE_POWER_USAGE_DETAIL; 310 } 311 312 @Override 313 protected String getLogTag() { 314 return TAG; 315 } 316 317 @Override 318 protected int getPreferenceScreenResId() { 319 return R.xml.power_usage_detail; 320 } 321 322 @Override 323 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 324 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 325 final Bundle bundle = getArguments(); 326 final int uid = bundle.getInt(EXTRA_UID, 0); 327 final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); 328 329 mBackgroundActivityPreferenceController = new BackgroundActivityPreferenceController( 330 context, this, uid, packageName); 331 controllers.add(mBackgroundActivityPreferenceController); 332 controllers.add(new BatteryOptimizationPreferenceController( 333 (SettingsActivity) getActivity(), this, packageName)); 334 mAppButtonsPreferenceController = new AppButtonsPreferenceController( 335 (SettingsActivity) getActivity(), this, getLifecycle(), packageName, mState, mDpm, 336 mUserManager, mPackageManager, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN); 337 controllers.add(mAppButtonsPreferenceController); 338 339 return controllers; 340 } 341 342 @Override 343 public void onActivityResult(int requestCode, int resultCode, Intent data) { 344 super.onActivityResult(requestCode, resultCode, data); 345 if (mAppButtonsPreferenceController != null) { 346 mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data); 347 } 348 } 349 350 @Override 351 public void handleDialogClick(int id) { 352 if (mAppButtonsPreferenceController != null) { 353 mAppButtonsPreferenceController.handleDialogClick(id); 354 } 355 } 356 357 @Override 358 public void onAnomalyHandled(Anomaly anomaly) { 359 mAnomalySummaryPreferenceController.hideHighUsagePreference(); 360 } 361 362 @Override 363 public Loader<List<Anomaly>> onCreateLoader(int id, Bundle args) { 364 return new AnomalyLoader(getContext(), mPackageName); 365 } 366 367 @Override 368 public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) { 369 final AnomalyUtils anomalyUtils = AnomalyUtils.getInstance(getContext()); 370 anomalyUtils.logAnomalies(mMetricsFeatureProvider, data, 371 MetricsEvent.FUELGAUGE_POWER_USAGE_DETAIL); 372 mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data); 373 } 374 375 @Override 376 public void onLoaderReset(Loader<List<Anomaly>> loader) { 377 378 } 379 380 @Override 381 public void onBatteryTipHandled(BatteryTip batteryTip) { 382 mBackgroundActivityPreferenceController.updateSummary( 383 findPreference(mBackgroundActivityPreferenceController.getPreferenceKey())); 384 } 385 } 386