Home | History | Annotate | Download | only in fuelgauge
      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