diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml new file mode 100644 index 00000000000..d32611ce969 --- /dev/null +++ b/res/xml/power_usage_advanced.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index d89653ae5ce..ac96151557e 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -66,8 +66,4 @@ - - diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java index 3ba5ee47fc0..5028264b596 100644 --- a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -149,7 +149,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro @Override public boolean isAvailable() { - return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST); + return true; } @Override @@ -186,12 +186,17 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro } } - public void refreshAppListGroup(BatteryStatsHelper statsHelper, boolean showAllApps, - CharSequence timeSequence) { + public void refreshAppListGroup(BatteryStatsHelper statsHelper, boolean showAllApps) { if (!isAvailable()) { return; } + mBatteryStatsHelper = statsHelper; + final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime( + mBatteryStatsHelper, System.currentTimeMillis()); + final CharSequence timeSequence = StringUtil.formatRelativeTime(mContext, + lastFullChargeTime, + false); final int resId = showAllApps ? R.string.power_usage_list_summary_device : R.string.power_usage_list_summary; mAppListGroup.setTitle(TextUtils.expandTemplate(mContext.getText(resId), timeSequence)); @@ -361,7 +366,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro final long usageTimeMs = sipper.usageTimeMs; if (usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) { final CharSequence timeSequence = - StringUtil.formatElapsedTime(mContext, usageTimeMs, false); + StringUtil.formatElapsedTime(mContext, usageTimeMs, false); preference.setSummary( (sipper.drainType != DrainType.APP || mBatteryUtils.shouldHideSipper(sipper)) ? timeSequence diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java new file mode 100644 index 00000000000..89f66bd8806 --- /dev/null +++ b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java @@ -0,0 +1,176 @@ +/* + * 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.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Bundle; +import android.provider.SearchIndexableResource; +import android.support.annotation.VisibleForTesting; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.utils.StringUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PowerUsageAdvanced extends PowerUsageBase { + private static final String TAG = "AdvancedBatteryUsage"; + private static final String KEY_BATTERY_GRAPH = "battery_graph"; + private static final String KEY_APP_LIST = "app_list"; + private static final String KEY_SHOW_ALL_APPS = "show_all_apps"; + @VisibleForTesting + static final int MENU_TOGGLE_APPS = Menu.FIRST + 1; + + @VisibleForTesting + BatteryHistoryPreference mHistPref; + private BatteryUtils mBatteryUtils; + private PowerUsageFeatureProvider mPowerUsageFeatureProvider; + private BatteryAppListPreferenceController mBatteryAppListPreferenceController; + @VisibleForTesting + boolean mShowAllApps = false; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + final Context context = getContext(); + + mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_GRAPH); + mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context); + mBatteryUtils = BatteryUtils.getInstance(context); + + // init the summary so other preferences won't have unnecessary move + updateHistPrefSummary(context); + restoreSavedInstance(icicle); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (getActivity().isChangingConfigurations()) { + BatteryEntry.clearUidCache(); + } + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.FUELGAUGE_BATTERY_HISTORY_DETAIL; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.power_usage_advanced; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE, + mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_TOGGLE_APPS: + mShowAllApps = !mShowAllApps; + item.setTitle(mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps); + mMetricsFeatureProvider.action(getContext(), + MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, + mShowAllApps); + restartBatteryStatsLoader(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @VisibleForTesting + void restoreSavedInstance(Bundle savedInstance) { + if (savedInstance != null) { + mShowAllApps = savedInstance.getBoolean(KEY_SHOW_ALL_APPS, false); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_SHOW_ALL_APPS, mShowAllApps); + } + + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + + mBatteryAppListPreferenceController = new BatteryAppListPreferenceController(context, + KEY_APP_LIST, getLifecycle(), (SettingsActivity) getActivity(), this); + controllers.add(mBatteryAppListPreferenceController); + + return controllers; + } + + @Override + protected void refreshUi() { + final Context context = getContext(); + if (context == null) { + return; + } + updatePreference(mHistPref); + updateHistPrefSummary(context); + + mBatteryAppListPreferenceController.refreshAppListGroup(mStatsHelper, mShowAllApps); + } + + private void updateHistPrefSummary(Context context) { + Intent batteryIntent = + context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + final boolean plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0; + + if (mPowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(context) && !plugged) { + mHistPref.setBottomSummary( + mPowerUsageFeatureProvider.getAdvancedUsageScreenInfoString()); + } else { + mHistPref.hideBottomSummary(); + } + } + + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.power_usage_advanced; + return Arrays.asList(sir); + } + }; + +} diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index bf0b627f910..8d70e46463f 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -101,7 +101,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList SparseArray> mAnomalySparseArray; @VisibleForTesting BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; - private BatteryAppListPreferenceController mBatteryAppListPreferenceController; private BatteryTipPreferenceController mBatteryTipPreferenceController; private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; @@ -231,9 +230,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList mBatteryHeaderPreferenceController = new BatteryHeaderPreferenceController( context, activity, this /* host */, lifecycle); controllers.add(mBatteryHeaderPreferenceController); - mBatteryAppListPreferenceController = new BatteryAppListPreferenceController(context, - KEY_APP_LIST, lifecycle, activity, this); - controllers.add(mBatteryAppListPreferenceController); mBatteryTipPreferenceController = new BatteryTipPreferenceController(context, KEY_BATTERY_TIP, (SettingsActivity) getActivity(), this /* fragment */, this /* BatteryTipListener */); @@ -294,11 +290,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList updateLastFullChargePreference(lastFullChargeTime); mScreenUsagePref.setSubtitle(StringUtil.formatElapsedTime(getContext(), mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false)); - - final CharSequence timeSequence = StringUtil.formatRelativeTime(context, lastFullChargeTime, - false); - mBatteryAppListPreferenceController.refreshAppListGroup(mStatsHelper, - false /* showAllApps */, timeSequence); } @VisibleForTesting diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java index b223a10ed21..8156428d136 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java @@ -202,20 +202,6 @@ public class BatteryAppListPreferenceControllerTest { assertThat(mPreferenceController.shouldHideSipper(mNormalBatterySipper)).isFalse(); } - @Test - public void testIsAvailable_featureOn_returnTrue() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST, true); - - assertThat(mPreferenceController.isAvailable()).isTrue(); - } - - @Test - public void testIsAvailable_featureOff_returnFalse() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST, false); - - assertThat(mPreferenceController.isAvailable()).isFalse(); - } - @Test public void testNeverUseFakeData() { assertThat(BatteryAppListPreferenceController.USE_FAKE_DATA).isFalse(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java new file mode 100644 index 00000000000..eba62522b80 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java @@ -0,0 +1,111 @@ +/* + * 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.ArgumentMatchers.nullable; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Bundle; +import android.support.v7.preference.PreferenceScreen; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PowerUsageAdvancedTest { + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Menu mMenu; + @Mock + private MenuInflater mMenuInflater; + @Mock + private MenuItem mToggleAppsMenu; + private Context mContext; + private PowerUsageAdvanced mFragment; + private FakeFeatureFactory mFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mToggleAppsMenu.getItemId()).thenReturn(PowerUsageAdvanced.MENU_TOGGLE_APPS); + + mFragment = spy(new PowerUsageAdvanced()); + mFragment.onAttach(mContext); + } + + @Test + public void testSaveInstanceState_showAllAppsRestored() { + Bundle bundle = new Bundle(); + mFragment.mShowAllApps = true; + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + + mFragment.onSaveInstanceState(bundle); + mFragment.restoreSavedInstance(bundle); + + assertThat(mFragment.mShowAllApps).isTrue(); + } + + @Test + public void testOptionsMenu_menuAppToggle_metricEventInvoked() { + mFragment.mShowAllApps = false; + doNothing().when(mFragment).restartBatteryStatsLoader(); + + mFragment.onOptionsItemSelected(mToggleAppsMenu); + + verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), + eq(MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE), eq(true)); + } + + @Test + public void testOptionsMenu_toggleAppsEnabled() { + when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled()) + .thenReturn(true); + mFragment.mShowAllApps = false; + + mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); + + verify(mMenu).add(Menu.NONE, PowerUsageAdvanced.MENU_TOGGLE_APPS, Menu.NONE, + R.string.show_all_apps); + } +}