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