diff --git a/res/layout/battery_header.xml b/res/layout/battery_header.xml new file mode 100644 index 00000000000..a2484c2ad3c --- /dev/null +++ b/res/layout/battery_header.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 6b835166a48..2bc83c1e960 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -311,4 +311,8 @@ 16dp + + + 66dp + 100dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 6cb164a0df2..fd476e593bb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4478,6 +4478,12 @@ Usage breakdown since last full charge + + Estimated time left + + + Estimation may change based on usage + %1$s since unplugged diff --git a/res/xml/account_type_settings.xml b/res/xml/account_type_settings.xml index 91e90feac0b..6663d7b85ab 100644 --- a/res/xml/account_type_settings.xml +++ b/res/xml/account_type_settings.xml @@ -38,6 +38,7 @@ + android:order="100" + android:selectable="false"/> diff --git a/res/xml/app_storage_settings.xml b/res/xml/app_storage_settings.xml index 3faf9c8bc64..0254b137ed9 100644 --- a/res/xml/app_storage_settings.xml +++ b/res/xml/app_storage_settings.xml @@ -30,6 +30,7 @@ diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index b4ac7e2c08d..b6808c000f4 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -20,8 +20,10 @@ android:title="@string/power_usage_summary_title" settings:keywords="@string/keywords_battery"> - + 0; } @@ -325,7 +351,14 @@ public class PowerUsageSummary extends PowerUsageBase { protected void refreshStats() { super.refreshStats(); - updatePreference(mHistPref); + + BatteryInfo.getBatteryInfo(getContext(), new BatteryInfo.Callback() { + @Override + public void onBatteryInfoLoaded(BatteryInfo info) { + updateHeaderPreference(info); + } + }); + cacheRemoveAllPrefs(mAppListGroup); mAppListGroup.setOrderingAsAdded(false); boolean addedSome = false; @@ -436,6 +469,27 @@ public class PowerUsageSummary extends PowerUsageBase { BatteryEntry.startRequestQueue(); } + @VisibleForTesting + void updateHeaderPreference(BatteryInfo info) { + final BatteryMeterView batteryView = (BatteryMeterView) mBatteryLayoutPref + .findViewById(R.id.battery_header_icon); + final TextView timeText = (TextView) mBatteryLayoutPref.findViewById(R.id.time); + final TextView summary1 = (TextView) mBatteryLayoutPref.findViewById(R.id.summary1); + final TextView summary2 = (TextView) mBatteryLayoutPref.findViewById(R.id.summary2); + final int visible = info.mBatteryLevel != 100 ? View.VISIBLE : View.INVISIBLE; + + if (info.remainingTimeUs != 0) { + timeText.setText(Utils.formatElapsedTime(getContext(), + info.remainingTimeUs / 1000, false)); + } else { + timeText.setText(info.remainingLabel != null ? + info.remainingLabel : info.batteryPercentString); + } + summary1.setVisibility(visible); + summary2.setVisibility(visible); + batteryView.setBatteryInfo(info.mBatteryLevel); + } + @VisibleForTesting void setUsageSummary(Preference preference, String usedTimePrefix, long usageTimeMs) { // Only show summary when usage time is longer than one minute @@ -482,6 +536,11 @@ public class PowerUsageSummary extends PowerUsageBase { return 0; } + @VisibleForTesting + void setBatteryLayoutPreference(LayoutPreference layoutPreference) { + mBatteryLayoutPref = layoutPreference; + } + private static List getFakeStats() { ArrayList stats = new ArrayList<>(); float use = 5; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryMeterViewTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryMeterViewTest.java new file mode 100644 index 00000000000..85b893ae493 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryMeterViewTest.java @@ -0,0 +1,65 @@ +/* + * 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 com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.testutils.shadow.SettingsShadowResources.SettingsShadowTheme; +import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import static org.mockito.Mockito.verify; + +@RunWith(SettingsRobolectricTestRunner.class) +// TODO: Consider making the shadow class set global using a robolectric.properties file. +@Config(manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = { + SettingsShadowResources.class, + SettingsShadowTheme.class, + ShadowDynamicIndexableContentMonitor.class + }) +public class BatteryMeterViewTest { + private static final int BATTERY_LEVEL = 100; + @Mock + private BatteryMeterView.BatteryMeterDrawable mDrawable; + private Context mContext; + private BatteryMeterView mBatteryMeterView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mBatteryMeterView = new BatteryMeterView(mContext); + mBatteryMeterView.setBatteryDrawable(mDrawable); + } + + @Test + public void testSetBatteryInfo_SetCorrectly() { + mBatteryMeterView.setBatteryInfo(BATTERY_LEVEL); + + verify(mDrawable).setBatteryLevel(BATTERY_LEVEL); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index 63cb5e1bdbb..1aacabdfc89 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -23,11 +23,15 @@ import android.text.format.DateUtils; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsImpl; import com.android.settings.R; import com.android.settings.TestConfig; +import com.android.settings.applications.LayoutPreference; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.BatteryInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +40,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.List; @@ -44,7 +49,6 @@ import static com.android.settings.fuelgauge.PowerUsageBase.MENU_STATS_REFRESH; import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADDITIONAL_BATTERY_INFO; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -58,8 +62,11 @@ import static org.mockito.Mockito.when; @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class PowerUsageSummaryTest { private static final String[] PACKAGE_NAMES = {"com.app1", "com.app2"}; + private static final String TIME_LEFT = "2h30min"; private static final int UID = 123; private static final int POWER_MAH = 100; + private static final int BATTERY_LEVEL_FULL = 100; + private static final int BATTERY_LEVEL_HALF = 50; private static final double BATTERY_SCREEN_USAGE = 300; private static final double PRECISION = 0.001; private static final Intent ADDITIONAL_BATTERY_INFO_INTENT = @@ -81,6 +88,18 @@ public class PowerUsageSummaryTest { private BatterySipper mScreenBatterySipper; @Mock private PowerGaugePreference mPreference; + @Mock + private LayoutPreference mBatteryLayoutPref; + @Mock + private BatteryMeterView mBatteryMeterView; + @Mock + private TextView mTimeText; + @Mock + private TextView mSummary1; + @Mock + private TextView mSummary2; + @Mock + private BatteryInfo mBatteryInfo; private TestFragment mFragment; private FakeFeatureFactory mFeatureFactory; @@ -111,6 +130,12 @@ public class PowerUsageSummaryTest { when(mNormalBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES); when(mNormalBatterySipper.getUid()).thenReturn(UID); mNormalBatterySipper.totalPowerMah = POWER_MAH; + when(mBatteryLayoutPref.findViewById(R.id.summary1)).thenReturn(mSummary1); + when(mBatteryLayoutPref.findViewById(R.id.summary2)).thenReturn(mSummary2); + when(mBatteryLayoutPref.findViewById(R.id.time)).thenReturn(mTimeText); + when(mBatteryLayoutPref.findViewById(R.id.battery_header_icon)) + .thenReturn(mBatteryMeterView); + mPowerUsageSummary.setBatteryLayoutPreference(mBatteryLayoutPref); mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE; @@ -230,6 +255,28 @@ public class PowerUsageSummaryTest { verify(mPreference).setSummary(anyString()); } + @Test + public void testUpdatePreference_BatteryFull_DoNotShowSummary() { + mBatteryInfo.mBatteryLevel = BATTERY_LEVEL_FULL; + mBatteryInfo.remainingLabel = TIME_LEFT; + mPowerUsageSummary.updateHeaderPreference(mBatteryInfo); + + verify(mSummary1).setVisibility(View.INVISIBLE); + verify(mSummary2).setVisibility(View.INVISIBLE); + verify(mTimeText).setText(mBatteryInfo.remainingLabel); + } + + @Test + public void testUpdatePreference_BatteryNotFull_ShowSummary() { + mBatteryInfo.mBatteryLevel = BATTERY_LEVEL_HALF; + mBatteryInfo.remainingLabel = TIME_LEFT; + mPowerUsageSummary.updateHeaderPreference(mBatteryInfo); + + verify(mSummary1).setVisibility(View.VISIBLE); + verify(mSummary2).setVisibility(View.VISIBLE); + verify(mTimeText).setText(mBatteryInfo.remainingLabel); + } + public static class TestFragment extends PowerUsageSummary { private Context mContext; diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java index 3a1e0b7b41f..cc55a4eb93b 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java @@ -7,6 +7,7 @@ import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.support.annotation.ArrayRes; import android.util.AttributeSet; import android.util.TypedValue; import org.robolectric.RuntimeEnvironment; @@ -59,6 +60,16 @@ public class SettingsShadowResources extends ShadowResources { return super.loadDrawable(value, id, theme); } + @Implementation + public int[] getIntArray(@ArrayRes int id) throws NotFoundException { + // The Robolectric isn't aware of resources in settingslib, so we need to stub it here + if (id == com.android.settings.R.array.batterymeter_bolt_points + || id == com.android.settings.R.array.batterymeter_plus_points) { + return new int[2]; + } + return directlyOn(realResources, Resources.class).getIntArray(id); + } + @Implements(Theme.class) public static class SettingsShadowTheme extends ShadowTheme {