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
This commit is contained in:
jackqdyulei
2017-03-08 19:35:02 -08:00
parent cb25394618
commit ddba966700
18 changed files with 992 additions and 40 deletions

View File

@@ -4417,8 +4417,12 @@
<!-- Title for the background activity setting, which allows a user to control whether an app can run in the background [CHAR_LIMIT=40] -->
<string name="background_activity_title">Background activity</string>
<!-- Summary for the background activity [CHAR_LIMIT=120] -->
<string name="background_activity_summary">Allow the app to run in the background</string>
<!-- Summary for the background activity when it is on [CHAR_LIMIT=120] -->
<string name="background_activity_summary_on">App can run in the background when not in use</string>
<!-- Summary for the background activity when it is off [CHAR_LIMIT=120] -->
<string name="background_activity_summary_off">App\'s background activity is limited when not in use</string>
<!-- Summary for the background activity when it is disabled [CHAR_LIMIT=120] -->
<string name="background_activity_summary_disabled">App not allowed to run in background</string>
<!-- Title for the screen usage in power use UI [CHAR_LIMIT=40] -->
<string name="device_screen_usage">Screen usage since full charge</string>
@@ -4632,13 +4636,25 @@
<!-- Description for battery usage info for an app, i.e. 60% used by facebook. [CHAR LIMIT=60] -->
<string name="battery_used_by"><xliff:g id="percent">%1$s</xliff:g> used by <xliff:g id="app">%2$s</xliff:g></string>
<!-- Description for percentage of battery usage for an app, i.e. Screen: 30% of overall battery. [CHAR LIMIT=60] -->
<string name="battery_overall_usage">%1$s of overall battery</string>
<string name="battery_overall_usage"><xliff:g id="percent">%1$s</xliff:g> of overall battery</string>
<!-- Description for battery usage detail information since last full charge. [CHAR LIMIT=120] -->
<string name="battery_detail_since_full_charge">Breakdown since last full charge</string>
<!-- Title for usage time since last full charge. [CHAR LIMIT=60] -->
<string name="battery_last_full_charge">Last full charge</string>
<!-- Description for text in battery footer. [CHAR LIMIT=120] -->
<string name="battery_footer_summary">Remaining battery time is approximate and can change based on usage</string>
<!-- Title for battery usage detail in foreground. [CHAR LIMIT=80] -->
<string name="battery_detail_foreground">While using app</string>
<!-- Title for battery usage detail in background. [CHAR LIMIT=80] -->
<string name="battery_detail_background">While in background</string>
<!-- Title for battery usage amount by this app. [CHAR LIMIT=80] -->
<string name="battery_detail_power_usage">Battery usage</string>
<!-- Description for battery usage amount, i.e. 16% of overall app usage(340 mAh). [CHAR LIMIT=120] -->
<string name="battery_detail_power_percentage"><xliff:g id="percent">%1$s</xliff:g> of total app usage (<xliff:g id="power">%2$d</xliff:g>mAh)</string>
<!-- Title for the battery usage group, which means all the battery data are calculated 'since full charge' [CHAR LIMIT=40] -->
<string name ="battery_detail_info_title">Since full charge</string>
<!-- Title for the battery management group [CHAR LIMIT=40] -->
<string name ="battery_detail_manage_title">Manage battery usage</string>
<!-- Description for battery time left, i.e. 50min Estimated time left. [CHAR LIMIT=80]-->
<string name="estimated_time_left">Estimated time left</string>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<com.android.settings.applications.LayoutPreference
android:key="header_view"
android:layout="@layout/app_details"
android:selectable="false"
android:order="-10000"/>
<com.android.settings.applications.LayoutPreference
android:key="action_buttons"
android:layout="@layout/app_action_buttons"
android:selectable="false"
android:order="-9999"/>
<PreferenceCategory
android:title="@string/battery_detail_info_title">
<Preference
android:key="app_usage_foreground"
android:title="@string/battery_detail_foreground"/>
<Preference
android:key="app_usage_background"
android:title="@string/battery_detail_background"/>
<Preference
android:key="app_power_usage"
android:title="@string/battery_detail_power_usage"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/battery_detail_manage_title">
<SwitchPreference
android:key="background_activity"
android:title="@string/background_activity_title"
android:selectable="true"/>
<Preference
android:key="battery_optimization"
android:title="@string/battery_detail_background"
android:summary="@string/high_power_off"
android:selectable="true"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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<PreferenceController> getPreferenceControllers(Context context) {
final List<PreferenceController> 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;
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -63,6 +63,10 @@ public class PowerGaugePreference extends TintablePreference {
notifyChanged();
}
public String getPercent() {
return mProgress.toString();
}
BatteryEntry getInfo() {
return mInfo;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
Answer<Void> callable = new Answer<Void>() {
@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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}