From ddba96670048a07b98e5c1e0d9f3d9bd4ebe9b38 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 8 Mar 2017 19:35:02 -0800 Subject: [PATCH] Revamp the battery usage details page. This cl adds AdvancedPowerUsageDetail to show the usage details page. The AdvancedPowerUsageDetail contains all the needed ui components: 1. App Header 2. Two buttons 3. Usage breakdown preference category 4. Power management preference category This cl also adds preference controller for two buttons but the detail implementation will be added in the following cl. Following cl will also remove previous detail page. Bug: 35810915 Test: RunSettingsRoboTests Change-Id: I17f95d1288762094671c0f148fa73367e51f175e --- res/values/strings.xml | 22 +- res/xml/power_usage_detail_ia.xml | 65 ++++++ src/com/android/settings/Utils.java | 12 + .../applications/InstalledAppDetails.java | 10 +- .../fuelgauge/AdvancedPowerUsageDetail.java | 204 +++++++++++++++++ .../AppButtonsPreferenceController.java | 87 ++++++++ ...ackgroundActivityPreferenceController.java | 8 +- ...tteryOptimizationPreferenceController.java | 69 ++++++ .../settings/fuelgauge/BatteryUtils.java | 89 ++++++++ .../fuelgauge/PowerGaugePreference.java | 4 + .../settings/fuelgauge/PowerUsageDetail.java | 1 + .../settings/fuelgauge/PowerUsageSummary.java | 4 +- ...randfather_not_implementing_index_provider | 1 + .../src/com/android/settings/UtilsTest.java | 25 +++ .../applications/InstalledAppDetailsTest.java | 25 --- .../AdvancedPowerUsageDetailTest.java | 210 ++++++++++++++++++ ...yOptimizationPreferenceControllerTest.java | 88 ++++++++ .../settings/fuelgauge/BatteryUtilsTest.java | 108 +++++++++ 18 files changed, 992 insertions(+), 40 deletions(-) create mode 100644 res/xml/power_usage_detail_ia.xml create mode 100644 src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java create mode 100644 src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java create mode 100644 src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceController.java create mode 100644 src/com/android/settings/fuelgauge/BatteryUtils.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 1631e9c6c63..a2713211668 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4417,8 +4417,12 @@ Background activity - - Allow the app to run in the background + + App can run in the background when not in use + + App\'s background activity is limited when not in use + + App not allowed to run in background Screen usage since full charge @@ -4632,13 +4636,25 @@ %1$s used by %2$s - %1$s of overall battery + %1$s of overall battery Breakdown since last full charge Last full charge Remaining battery time is approximate and can change based on usage + + While using app + + While in background + + Battery usage + + %1$s of total app usage (%2$dmAh) + + Since full charge + + Manage battery usage Estimated time left diff --git a/res/xml/power_usage_detail_ia.xml b/res/xml/power_usage_detail_ia.xml new file mode 100644 index 00000000000..cfaa7124801 --- /dev/null +++ b/res/xml/power_usage_detail_ia.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 909ddfe7f8f..4c8fb2d3414 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -74,6 +74,7 @@ import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; import android.provider.Settings; import android.service.persistentdata.PersistentDataBlockManager; +import android.support.annotation.StringRes; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; import android.support.v7.preference.PreferenceManager; @@ -1241,6 +1242,17 @@ public final class Utils extends com.android.settingslib.Utils { return isVolumeValid(volume) ? volume : null; } + /** + * Return the resource id to represent the install status for an app + */ + @StringRes + public static int getInstallationStatus(ApplicationInfo info) { + if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { + return R.string.not_installed; + } + return info.enabled ? R.string.installed : R.string.disabled; + } + private static boolean isVolumeValid(VolumeInfo volume) { return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE) && volume.isMountedReadable(); diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index e00ba92bc6e..dac10e7f5a9 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -546,7 +546,7 @@ public class InstalledAppDetails extends AppInfoBase .newAppHeaderController(this, appSnippet) .setLabel(mAppEntry) .setIcon(mAppEntry) - .setSummary(getString(getInstallationStatus(mAppEntry.info))) + .setSummary(getString(Utils.getInstallationStatus(mAppEntry.info))) .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) .done(false /* rebindActions */); mVersionPreference.setSummary(getString(R.string.version_text, pkgInfo.versionName)); @@ -574,14 +574,6 @@ public class InstalledAppDetails extends AppInfoBase return showIt; } - @VisibleForTesting - int getInstallationStatus(ApplicationInfo info) { - if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { - return R.string.not_installed; - } - return info.enabled ? R.string.installed : R.string.disabled; - } - private boolean signaturesMatch(String pkg1, String pkg2) { if (pkg1 != null && pkg2 != null) { try { diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java new file mode 100644 index 00000000000..ece6b78930c --- /dev/null +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import android.content.Context; +import android.os.BatteryStats; +import android.os.Bundle; +import android.os.SystemClock; +import android.os.UserHandle; +import android.support.annotation.VisibleForTesting; +import android.support.v14.preference.PreferenceFragment; +import android.support.v7.preference.Preference; +import android.view.View; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.internal.util.ArrayUtils; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.Utils; +import com.android.settings.applications.AppHeaderController; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.core.PreferenceController; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.applications.ApplicationsState; + +import java.util.ArrayList; +import java.util.List; + +/** + * Power usage detail fragment for each app, this fragment contains + * + * 1. Detail battery usage information for app(i.e. usage time, usage amount) + * 2. Battery related controls for app(i.e uninstall, force stop) + * + * This fragment will replace {@link PowerUsageDetail} + */ +public class AdvancedPowerUsageDetail extends PowerUsageBase { + + public static final String TAG = "AdvancedPowerUsageDetail"; + public static final String EXTRA_UID = "extra_uid"; + public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; + public static final String EXTRA_FOREGROUND_TIME = "extra_foreground_time"; + public static final String EXTRA_BACKGROUND_TIME = "extra_background_time"; + public static final String EXTRA_LABEL = "extra_label"; + public static final String EXTRA_ICON_ID = "extra_icon_id"; + public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent"; + public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount"; + + private static final String KEY_PREF_FOREGROUND = "app_usage_foreground"; + private static final String KEY_PREF_BACKGROUND = "app_usage_background"; + private static final String KEY_PREF_POWER_USAGE = "app_power_usage"; + private static final String KEY_PREF_HEADER = "header_view"; + + @VisibleForTesting + LayoutPreference mHeaderPreference; + @VisibleForTesting + ApplicationsState mState; + @VisibleForTesting + ApplicationsState.AppEntry mAppEntry; + + private Preference mForegroundPreference; + private Preference mBackgroundPreference; + private Preference mPowerUsagePreference; + + public static void startBatteryDetailPage(SettingsActivity caller, PreferenceFragment fragment, + BatteryStatsHelper helper, int which, BatteryEntry entry, String usagePercent) { + // Initialize mStats if necessary. + helper.getStats(); + + final Bundle args = new Bundle(); + final BatterySipper sipper = entry.sipper; + final BatteryStats.Uid uid = sipper.uidObj; + + final long backgroundTimeMs = BatteryUtils.getProcessTimeMs( + BatteryUtils.StatusType.BACKGROUND, uid, which); + final long foregroundTimeMs = BatteryUtils.getProcessTimeMs( + BatteryUtils.StatusType.FOREGROUND, uid, which); + + if (ArrayUtils.isEmpty(sipper.mPackages)) { + // populate data for system app + args.putString(EXTRA_LABEL, entry.getLabel()); + args.putInt(EXTRA_ICON_ID, entry.iconId); + args.putString(EXTRA_PACKAGE_NAME, null); + } else { + // populate data for normal app + args.putString(EXTRA_PACKAGE_NAME, sipper.mPackages[0]); + } + + args.putInt(EXTRA_UID, sipper.getUid()); + args.putLong(EXTRA_BACKGROUND_TIME, backgroundTimeMs); + args.putLong(EXTRA_FOREGROUND_TIME, foregroundTimeMs); + args.putString(EXTRA_POWER_USAGE_PERCENT, usagePercent); + args.putInt(EXTRA_POWER_USAGE_AMOUNT, (int) sipper.totalPowerMah); + + caller.startPreferencePanelAsUser(fragment, AdvancedPowerUsageDetail.class.getName(), args, + R.string.details_title, null, new UserHandle(UserHandle.myUserId())); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mForegroundPreference = findPreference(KEY_PREF_FOREGROUND); + mBackgroundPreference = findPreference(KEY_PREF_BACKGROUND); + mPowerUsagePreference = findPreference(KEY_PREF_POWER_USAGE); + mHeaderPreference = (LayoutPreference) findPreference(KEY_PREF_HEADER); + mState = ApplicationsState.getInstance(getActivity().getApplication()); + + final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME); + if (packageName != null) { + mAppEntry = mState.getEntry(packageName, UserHandle.myUserId()); + } + } + + @Override + public void onResume() { + super.onResume(); + + initHeader(); + + final Bundle bundle = getArguments(); + final Context context = getContext(); + + final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME); + final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME); + final String usagePercent = bundle.getString(EXTRA_POWER_USAGE_PERCENT); + final int powerMah = bundle.getInt(EXTRA_POWER_USAGE_AMOUNT); + mForegroundPreference.setSummary(Utils.formatElapsedTime(context, foregroundTimeMs, false)); + mBackgroundPreference.setSummary(Utils.formatElapsedTime(context, backgroundTimeMs, false)); + mPowerUsagePreference.setSummary( + getString(R.string.battery_detail_power_percentage, usagePercent, powerMah)); + } + + @VisibleForTesting + void initHeader() { + final View appSnippet = mHeaderPreference.findViewById(R.id.app_snippet); + final Context context = getContext(); + final Bundle bundle = getArguments(); + AppHeaderController controller = FeatureFactory.getFactory(context) + .getApplicationFeatureProvider(context) + .newAppHeaderController(this, appSnippet) + .setButtonActions(AppHeaderController.ActionType.ACTION_NONE, + AppHeaderController.ActionType.ACTION_NONE); + + if (mAppEntry == null) { + controller.setLabel(bundle.getString(EXTRA_LABEL)); + controller.setIcon(getContext().getDrawable(bundle.getInt(EXTRA_ICON_ID))); + } else { + mState.ensureIcon(mAppEntry); + controller.setLabel(mAppEntry); + controller.setIcon(mAppEntry); + controller.setSummary(getString(Utils.getInstallationStatus(mAppEntry.info))); + } + + controller.done(true /* rebindActions */); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.FUELGAUGE_POWER_USAGE_DETAIL; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.power_usage_detail_ia; + } + + @Override + protected List getPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + final Bundle bundle = getArguments(); + final int uid = bundle.getInt(EXTRA_UID, 0); + final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); + + controllers.add(new BackgroundActivityPreferenceController(context, uid)); + controllers.add(new BatteryOptimizationPreferenceController( + (SettingsActivity) getActivity(), this)); + controllers.add( + new AppButtonsPreferenceController(getActivity(), getLifecycle(), packageName)); + + return controllers; + } +} diff --git a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java new file mode 100644 index 00000000000..b02c8c5edbf --- /dev/null +++ b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import android.app.Activity; +import android.os.UserHandle; +import android.support.v7.preference.PreferenceScreen; +import android.view.View; +import android.widget.Button; + +import com.android.settings.R; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.core.PreferenceController; +import com.android.settings.core.lifecycle.Lifecycle; +import com.android.settings.core.lifecycle.LifecycleObserver; +import com.android.settings.core.lifecycle.events.OnResume; +import com.android.settingslib.applications.ApplicationsState; + +/** + * Controller to control the uninstall button and forcestop button + */ +//TODO(b/35810915): refine the button logic and make InstalledAppDetails use this controller +//TODO(b/35810915): add test for this file +public class AppButtonsPreferenceController extends PreferenceController implements + LifecycleObserver, OnResume { + private static final String KEY_ACTION_BUTTONS = "action_buttons"; + + private ApplicationsState.AppEntry mAppEntry; + private LayoutPreference mButtonsPref; + private Button mForceStopButton; + private Button mUninstallButton; + + public AppButtonsPreferenceController(Activity activity, Lifecycle lifecycle, + String packageName) { + super(activity); + + lifecycle.addObserver(this); + ApplicationsState state = ApplicationsState.getInstance(activity.getApplication()); + + if (packageName != null) { + mAppEntry = state.getEntry(packageName, UserHandle.myUserId()); + } + } + + @Override + public boolean isAvailable() { + return mAppEntry != null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + mButtonsPref = (LayoutPreference) screen.findPreference(KEY_ACTION_BUTTONS); + + mUninstallButton = (Button) mButtonsPref.findViewById(R.id.left_button); + mUninstallButton.setText(R.string.uninstall_text); + + mForceStopButton = (Button) mButtonsPref.findViewById(R.id.right_button); + mForceStopButton.setText(R.string.force_stop); + } + } + + @Override + public String getPreferenceKey() { + return KEY_ACTION_BUTTONS; + } + + @Override + public void onResume() { + //TODO(b/35810915): check and update the status of buttons + } +} diff --git a/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java b/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java index 0f083985b68..c249676084a 100644 --- a/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java @@ -23,6 +23,8 @@ import android.support.annotation.VisibleForTesting; import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.Preference; import android.util.Log; + +import com.android.settings.R; import com.android.settings.core.PreferenceController; /** @@ -56,8 +58,12 @@ public class BackgroundActivityPreferenceController extends PreferenceController if (mode == AppOpsManager.MODE_ERRORED) { preference.setEnabled(false); + preference.setSummary(R.string.background_activity_summary_disabled); } else { - ((SwitchPreference) preference).setChecked(mode != AppOpsManager.MODE_IGNORED); + final boolean checked = mode != AppOpsManager.MODE_IGNORED; + ((SwitchPreference) preference).setChecked(checked); + preference.setSummary(checked ? R.string.background_activity_summary_on + : R.string.background_activity_summary_off); } } diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceController.java new file mode 100644 index 00000000000..946a9b89292 --- /dev/null +++ b/src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceController.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.Settings; +import com.android.settings.SettingsActivity; +import com.android.settings.applications.ManageApplications; +import com.android.settings.core.PreferenceController; + +/** + * Controller that jumps to high power optimization fragment + */ +public class BatteryOptimizationPreferenceController extends PreferenceController { + + private static final String KEY_BACKGROUND_ACTIVITY = "battery_optimization"; + + private Fragment mFragment; + private SettingsActivity mSettingsActivity; + + public BatteryOptimizationPreferenceController(SettingsActivity settingsActivity, + Fragment fragment) { + super(settingsActivity); + mFragment = fragment; + mSettingsActivity = settingsActivity; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_BACKGROUND_ACTIVITY; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!KEY_BACKGROUND_ACTIVITY.equals(preference.getKey())) { + return false; + } + + Bundle args = new Bundle(1); + args.putString(ManageApplications.EXTRA_CLASSNAME, + Settings.HighPowerApplicationsActivity.class.getName()); + mSettingsActivity.startPreferencePanel(mFragment, ManageApplications.class.getName(), args, + R.string.high_power_apps, null, null, 0); + return true; + } +} diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java new file mode 100644 index 00000000000..85bc0fda5f1 --- /dev/null +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.fuelgauge; + +import android.annotation.IntDef; +import android.os.BatteryStats; +import android.os.SystemClock; +import android.support.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Utils for battery operation + */ +public class BatteryUtils { + @Retention(RetentionPolicy.SOURCE) + @IntDef({StatusType.FOREGROUND, + StatusType.BACKGROUND, + StatusType.ALL + }) + public @interface StatusType { + int FOREGROUND = 0; + int BACKGROUND = 1; + int ALL = 2; + } + + public static long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid, + int which) { + if (uid == null) { + return 0; + } + + switch (type) { + case StatusType.FOREGROUND: + return getProcessForegroundTimeMs(uid, which); + case StatusType.BACKGROUND: + return getProcessBackgroundTimeMs(uid, which); + case StatusType.ALL: + return getProcessForegroundTimeMs(uid, which) + + getProcessBackgroundTimeMs(uid, which); + } + return 0; + } + + private static long getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which) { + final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime()); + final long timeUs = uid.getProcessStateTime( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, rawRealTimeUs, which); + return convertUsToMs(timeUs); + } + + private static long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) { + final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime()); + final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP, + BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, + BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING, + BatteryStats.Uid.PROCESS_STATE_FOREGROUND}; + long timeUs = 0; + for (int type : foregroundTypes) { + timeUs += uid.getProcessStateTime(type, rawRealTimeUs, which); + } + + return convertUsToMs(timeUs); + } + + private static long convertUsToMs(long timeUs) { + return timeUs / 1000; + } + + private static long convertMsToUs(long timeMs) { + return timeMs * 1000; + } + +} + diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java index fe7ef6eea5a..d4f2dd2c6a2 100644 --- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java +++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java @@ -63,6 +63,10 @@ public class PowerGaugePreference extends TintablePreference { notifyChanged(); } + public String getPercent() { + return mProgress.toString(); + } + BatteryEntry getInfo() { return mInfo; } diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java index 956b27935d4..f0d0a104a56 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java @@ -69,6 +69,7 @@ import java.io.Writer; import java.util.ArrayList; import java.util.List; +// TODO(b/35810915): Delete this page once ag/1971493 is done. public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickListener { // Note: Must match the sequence of the DrainType diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 1ffc594b379..b0e8fb0aeaa 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -165,8 +165,8 @@ public class PowerUsageSummary extends PowerUsageBase { } PowerGaugePreference pgp = (PowerGaugePreference) preference; BatteryEntry entry = pgp.getInfo(); - PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), this, - mStatsHelper, mStatsType, entry, true, true); + AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), + this, mStatsHelper, mStatsType, entry, pgp.getPercent()); return super.onPreferenceTreeClick(preference); } diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 778440d4990..240b8eb18fb 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -4,5 +4,6 @@ com.android.settings.notification.ZenModePrioritySettings com.android.settings.inputmethod.InputAndGestureSettings com.android.settings.accounts.AccountDetailDashboardFragment com.android.settings.fuelgauge.PowerUsageDetail +com.android.settings.fuelgauge.AdvancedPowerUsageDetail com.android.settings.deviceinfo.StorageProfileFragment com.android.settings.wifi.details.WifiNetworkDetailsFragment diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java index 324e75104b5..8b7605c2384 100644 --- a/tests/robotests/src/com/android/settings/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/UtilsTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; @@ -116,4 +117,28 @@ public class UtilsTest { Utils.maybeInitializeVolume(storageManager, new Bundle()); } + + @Test + public void getInstallationStatus_notInstalled_shouldReturnUninstalled() { + assertThat(Utils.getInstallationStatus(new ApplicationInfo())) + .isEqualTo(R.string.not_installed); + } + + @Test + public void getInstallationStatus_enabled_shouldReturnInstalled() { + final ApplicationInfo info = new ApplicationInfo(); + info.flags = ApplicationInfo.FLAG_INSTALLED; + info.enabled = true; + + assertThat(Utils.getInstallationStatus(info)).isEqualTo(R.string.installed); + } + + @Test + public void getInstallationStatus_disabled_shouldReturnDisabled() { + final ApplicationInfo info = new ApplicationInfo(); + info.flags = ApplicationInfo.FLAG_INSTALLED; + info.enabled = false; + + assertThat(Utils.getInstallationStatus(info)).isEqualTo(R.string.disabled); + } } diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java index b0cd8d5b9ef..bf00889ab0e 100644 --- a/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java @@ -67,31 +67,6 @@ public final class InstalledAppDetailsTest { mAppDetail = new InstalledAppDetails(); } - @Test - public void getInstallationStatus_notInstalled_shouldReturnUninstalled() { - - assertThat(mAppDetail.getInstallationStatus(new ApplicationInfo())) - .isEqualTo(R.string.not_installed); - } - - @Test - public void getInstallationStatus_enabled_shouldReturnInstalled() { - final ApplicationInfo info = new ApplicationInfo(); - info.flags = ApplicationInfo.FLAG_INSTALLED; - info.enabled = true; - - assertThat(mAppDetail.getInstallationStatus(info)).isEqualTo(R.string.installed); - } - - @Test - public void getInstallationStatus_disabled_shouldReturnDisabled() { - final ApplicationInfo info = new ApplicationInfo(); - info.flags = ApplicationInfo.FLAG_INSTALLED; - info.enabled = false; - - assertThat(mAppDetail.getInstallationStatus(info)).isEqualTo(R.string.disabled); - } - @Test public void shouldShowUninstallForAll_installForOneOtherUserOnly_shouldReturnTrue() { when(mDevicePolicyManager.packageHasActiveAdmins(anyString())).thenReturn(false); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java new file mode 100644 index 00000000000..8b5ce192d17 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Fragment; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.graphics.drawable.Drawable; +import android.os.BatteryStats; +import android.os.Bundle; +import android.os.UserHandle; +import android.view.View; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppHeaderController; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.applications.ApplicationsState; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class AdvancedPowerUsageDetailTest { + private static final String APP_LABEL = "app label"; + private static final String SUMMARY = "summary"; + private static final String[] PACKAGE_NAME = {"com.android.app"}; + private static final String USAGE_PERCENT = "16"; + private static final int ICON_ID = 123; + private static final int UID = 1; + private static final long BACKGROUND_TIME_US = 100 * 1000; + private static final long FOREGROUND_TIME_US = 200 * 1000; + private static final long BACKGROUND_TIME_MS = 100; + private static final long FOREGROUND_TIME_MS = 200; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock + private AppHeaderController mAppHeaderController; + @Mock + private LayoutPreference mHeaderPreference; + @Mock + private ApplicationsState mState; + @Mock + private ApplicationsState.AppEntry mAppEntry; + @Mock + private Drawable mIconDrawable; + @Mock + private Bundle mBundle; + @Mock + private BatteryEntry mBatteryEntry; + @Mock + private BatterySipper mBatterySipper; + @Mock + private BatteryStatsHelper mBatteryStatsHelper; + @Mock + private BatteryStats.Uid mUid; + private Bundle mTestBundle; + private AdvancedPowerUsageDetail mFragment; + private FakeFeatureFactory mFeatureFactory; + private SettingsActivity mTestActivity; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(mContext); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + + mFragment = spy(new AdvancedPowerUsageDetail()); + doReturn(mContext).when(mFragment).getContext(); + doReturn(SUMMARY).when(mFragment).getString(anyInt()); + doReturn(APP_LABEL).when(mBundle).getString(anyString()); + doReturn(mBundle).when(mFragment).getArguments(); + + doReturn(mAppHeaderController).when(mFeatureFactory.applicationFeatureProvider) + .newAppHeaderController(any(Fragment.class), any(View.class)); + doReturn(mAppHeaderController).when(mAppHeaderController).setButtonActions(anyInt(), + anyInt()); + doReturn(mAppHeaderController).when(mAppHeaderController).setIcon(any(Drawable.class)); + doReturn(mAppHeaderController).when(mAppHeaderController).setIcon(any( + ApplicationsState.AppEntry.class)); + doReturn(mAppHeaderController).when(mAppHeaderController).setLabel(anyString()); + doReturn(mAppHeaderController).when(mAppHeaderController).setLabel(any( + ApplicationsState.AppEntry.class)); + doReturn(mAppHeaderController).when(mAppHeaderController).setSummary(anyString()); + + + doReturn(UID).when(mBatterySipper).getUid(); + doReturn(APP_LABEL).when(mBatteryEntry).getLabel(); + doReturn(BACKGROUND_TIME_US).when(mUid).getProcessStateTime( + eq(BatteryStats.Uid.PROCESS_STATE_BACKGROUND), anyLong(), anyInt()); + doReturn(FOREGROUND_TIME_US).when(mUid).getProcessStateTime( + eq(BatteryStats.Uid.PROCESS_STATE_FOREGROUND), anyLong(), anyInt()); + ReflectionHelpers.setField(mBatteryEntry, "sipper", mBatterySipper); + mBatteryEntry.iconId = ICON_ID; + mBatterySipper.uidObj = mUid; + + mFragment.mHeaderPreference = mHeaderPreference; + mFragment.mState = mState; + mAppEntry.info = mock(ApplicationInfo.class); + + mTestActivity = spy(new SettingsActivity()); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(Bundle.class); + + Answer callable = new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + mBundle = captor.getValue(); + return null; + } + }; + doAnswer(callable).when(mTestActivity).startPreferencePanelAsUser(any(), anyString(), + captor.capture(), anyInt(), any(), any()); + } + + @Test + public void testInitHeader_NoAppEntry_BuildByBundle() { + mFragment.mAppEntry = null; + mFragment.initHeader(); + + verify(mAppHeaderController).setIcon(any(Drawable.class)); + verify(mAppHeaderController).setLabel(APP_LABEL); + } + + @Test + public void testInitHeader_HasAppEntry_BuildByAppEntry() { + mFragment.mAppEntry = mAppEntry; + mFragment.initHeader(); + + verify(mAppHeaderController).setIcon(mAppEntry); + verify(mAppHeaderController).setLabel(mAppEntry); + } + + @Test + public void testStartBatteryDetailPage_hasBasicData() { + AdvancedPowerUsageDetail.startBatteryDetailPage(mTestActivity, null, mBatteryStatsHelper, 0, + mBatteryEntry, USAGE_PERCENT); + + assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID); + assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME)).isEqualTo( + BACKGROUND_TIME_MS); + assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME)).isEqualTo( + FOREGROUND_TIME_MS); + assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT)).isEqualTo( + USAGE_PERCENT); + } + + @Test + public void testStartBatteryDetailPage_NormalApp() { + mBatterySipper.mPackages = PACKAGE_NAME; + AdvancedPowerUsageDetail.startBatteryDetailPage(mTestActivity, null, mBatteryStatsHelper, 0, + mBatteryEntry, USAGE_PERCENT); + + assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME)).isEqualTo( + PACKAGE_NAME[0]); + } + + @Test + public void testStartBatteryDetailPage_SystemApp() { + mBatterySipper.mPackages = null; + AdvancedPowerUsageDetail.startBatteryDetailPage(mTestActivity, null, mBatteryStatsHelper, 0, + mBatteryEntry, USAGE_PERCENT); + + assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_LABEL)).isEqualTo(APP_LABEL); + assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_ICON_ID)).isEqualTo(ICON_ID); + assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME)).isEqualTo(null); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceControllerTest.java new file mode 100644 index 00000000000..2c5296aca48 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizationPreferenceControllerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Fragment; +import android.os.Bundle; +import android.support.v7.preference.Preference; + +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BatteryOptimizationPreferenceControllerTest { + private static final String KEY_OPTIMIZATION = "battery_optimization"; + private static final String KEY_OTHER = "other"; + @Mock + private SettingsActivity mSettingsActivity; + @Mock + private Fragment mFragment; + @Mock + private Preference mPreference; + + private BatteryOptimizationPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mController = new BatteryOptimizationPreferenceController(mSettingsActivity, mFragment); + } + + @Test + public void testHandlePreferenceTreeClick_OptimizationPreference_HandleClick() { + when(mPreference.getKey()).thenReturn(KEY_OPTIMIZATION); + + final boolean handled = mController.handlePreferenceTreeClick(mPreference); + + assertThat(handled).isTrue(); + verify(mSettingsActivity).startPreferencePanel(any(Fragment.class), + anyString(), any(Bundle.class), anyInt(), any(CharSequence.class), + any(Fragment.class), anyInt()); + } + + @Test + public void testHandlePreferenceTreeClick_OtherPreference_NotHandleClick() { + when(mPreference.getKey()).thenReturn(KEY_OTHER); + + final boolean handled = mController.handlePreferenceTreeClick(mPreference); + + assertThat(handled).isFalse(); + verify(mSettingsActivity, never()).startPreferencePanel(any(Fragment.class), + anyString(), any(Bundle.class), anyInt(), any(CharSequence.class), + any(Fragment.class), anyInt()); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java new file mode 100644 index 00000000000..672cc903fd4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.fuelgauge; + +import android.os.BatteryStats; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND; +import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND; +import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; +import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Matchers.eq; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BatteryUtilsTest { + // unit that used to converted ms to us + private static final long UNIT = 1000; + private static final long TIME_STATE_TOP = 1500 * UNIT; + private static final long TIME_STATE_FOREGROUND_SERVICE = 2000 * UNIT; + private static final long TIME_STATE_TOP_SLEEPING = 2500 * UNIT; + private static final long TIME_STATE_FOREGROUND = 3000 * UNIT; + private static final long TIME_STATE_BACKGROUND = 6000 * UNIT; + + private static final long TIME_EXPECTED_FOREGROUND = 9000; + private static final long TIME_EXPECTED_BACKGROUND = 6000; + private static final long TIME_EXPECTED_ALL = 15000; + + @Mock + BatteryStats.Uid mUid; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(TIME_STATE_TOP).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP), anyLong(), + anyInt()); + doReturn(TIME_STATE_FOREGROUND_SERVICE).when(mUid).getProcessStateTime( + eq(PROCESS_STATE_FOREGROUND_SERVICE), anyLong(), anyInt()); + doReturn(TIME_STATE_TOP_SLEEPING).when(mUid).getProcessStateTime( + eq(PROCESS_STATE_TOP_SLEEPING), anyLong(), anyInt()); + doReturn(TIME_STATE_FOREGROUND).when(mUid).getProcessStateTime(eq(PROCESS_STATE_FOREGROUND), + anyLong(), anyInt()); + doReturn(TIME_STATE_BACKGROUND).when(mUid).getProcessStateTime(eq(PROCESS_STATE_BACKGROUND), + anyLong(), anyInt()); + } + + @Test + public void testGetProcessTimeMs_typeForeground_timeCorrect() { + final long time = BatteryUtils.getProcessTimeMs(BatteryUtils.StatusType.FOREGROUND, mUid, + BatteryStats.STATS_SINCE_CHARGED); + + assertThat(time).isEqualTo(TIME_EXPECTED_FOREGROUND); + } + + @Test + public void testGetProcessTimeMs_typeBackground_timeCorrect() { + final long time = BatteryUtils.getProcessTimeMs(BatteryUtils.StatusType.BACKGROUND, mUid, + BatteryStats.STATS_SINCE_CHARGED); + + assertThat(time).isEqualTo(TIME_EXPECTED_BACKGROUND); + } + + @Test + public void testGetProcessTimeMs_typeAll_timeCorrect() { + final long time = BatteryUtils.getProcessTimeMs(BatteryUtils.StatusType.ALL, mUid, + BatteryStats.STATS_SINCE_CHARGED); + + assertThat(time).isEqualTo(TIME_EXPECTED_ALL); + } + + @Test + public void testGetProcessTimeMs_uidNull_returnZero() { + final long time = BatteryUtils.getProcessTimeMs(BatteryUtils.StatusType.ALL, null, + BatteryStats.STATS_SINCE_CHARGED); + + assertThat(time).isEqualTo(0); + } +}