Merge changes Ifa91ad93,I14f9696a into pi-dev
am: d067cbcfc9

Change-Id: Idddf18d56322a274145be20a0c6858fcd6cd78e0
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
index b9194b4..fc2bbdf 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
@@ -67,7 +67,7 @@
         final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(mBatteryStatsHelper, TAG);
         final Context context = getContext();
 
-        tips.add(new LowBatteryDetector(policy, batteryInfo).detect());
+        tips.add(new LowBatteryDetector(context, policy, batteryInfo).detect());
         tips.add(new HighUsageDetector(context, policy, mBatteryStatsHelper,
                 batteryInfo.discharging).detect());
         tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect());
@@ -87,7 +87,8 @@
         final List<BatteryTip> tips = new ArrayList<>();
         tips.add(new SummaryTip(BatteryTip.StateType.NEW,
                 Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN));
-        tips.add(new LowBatteryTip(BatteryTip.StateType.NEW));
+        tips.add(new LowBatteryTip(BatteryTip.StateType.NEW, false /* powerSaveModeOn */,
+                "Fake data"));
 
         return tips;
     }
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java
index 5520bf3..1c7c760 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicy.java
@@ -50,6 +50,7 @@
     private static final String KEY_TEST_BATTERY_SAVER_TIP = "test_battery_saver_tip";
     private static final String KEY_TEST_HIGH_USAGE_TIP = "test_high_usage_tip";
     private static final String KEY_TEST_SMART_BATTERY_TIP = "test_smart_battery_tip";
+    private static final String KEY_TEST_LOW_BATTERY_TIP = "test_low_battery_tip";
 
     /**
      * {@code true} if general battery tip is enabled
@@ -192,6 +193,14 @@
      */
     public final boolean testSmartBatteryTip;
 
+    /**
+     * {@code true} if we want to test low battery tip.
+     *
+     * @see Settings.Global#BATTERY_TIP_CONSTANTS
+     * @see #KEY_TEST_LOW_BATTERY_TIP
+     */
+    public final boolean testLowBatteryTip;
+
     private final KeyValueListParser mParser;
 
     public BatteryTipPolicy(Context context) {
@@ -222,13 +231,14 @@
         reducedBatteryEnabled = mParser.getBoolean(KEY_REDUCED_BATTERY_ENABLED, false);
         reducedBatteryPercent = mParser.getInt(KEY_REDUCED_BATTERY_PERCENT, 50);
         lowBatteryEnabled = mParser.getBoolean(KEY_LOW_BATTERY_ENABLED, false);
-        lowBatteryHour = mParser.getInt(KEY_LOW_BATTERY_HOUR, 16);
+        lowBatteryHour = mParser.getInt(KEY_LOW_BATTERY_HOUR, 3);
         dataHistoryRetainDay = mParser.getInt(KEY_DATA_HISTORY_RETAIN_DAY, 30);
         excessiveBgDrainPercentage = mParser.getInt(KEY_EXCESSIVE_BG_DRAIN_PERCENTAGE, 10);
 
         testBatterySaverTip = mParser.getBoolean(KEY_TEST_BATTERY_SAVER_TIP, false);
         testHighUsageTip = mParser.getBoolean(KEY_TEST_HIGH_USAGE_TIP, false);
         testSmartBatteryTip = mParser.getBoolean(KEY_TEST_SMART_BATTERY_TIP, false);
+        testLowBatteryTip = mParser.getBoolean(KEY_TEST_LOW_BATTERY_TIP, false);
     }
 
 }
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
index 2a6302e..c3f9b07 100644
--- a/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
@@ -16,31 +16,52 @@
 
 package com.android.settings.fuelgauge.batterytip.detectors;
 
-import android.text.format.DateUtils;
+import android.content.Context;
+import android.os.PowerManager;
 
 import com.android.settings.fuelgauge.BatteryInfo;
 import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
 import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Detect whether the battery is too low
  */
 public class LowBatteryDetector implements BatteryTipDetector {
     private BatteryInfo mBatteryInfo;
     private BatteryTipPolicy mPolicy;
+    private PowerManager mPowerManager;
+    private int mWarningLevel;
 
-    public LowBatteryDetector(BatteryTipPolicy policy, BatteryInfo batteryInfo) {
+    public LowBatteryDetector(Context context, BatteryTipPolicy policy, BatteryInfo batteryInfo) {
         mPolicy = policy;
         mBatteryInfo = batteryInfo;
+        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWarningLevel = context.getResources().getInteger(
+                com.android.internal.R.integer.config_lowBatteryWarningLevel);
     }
 
     @Override
     public BatteryTip detect() {
-        // Show it if battery life is less than mPolicy.lowBatteryHour
-        final boolean isShown = mPolicy.lowBatteryEnabled && mBatteryInfo.discharging
-                && mBatteryInfo.remainingTimeUs < mPolicy.lowBatteryHour * DateUtils.HOUR_IN_MILLIS;
+        final boolean powerSaveModeOn = mPowerManager.isPowerSaveMode();
+        final boolean lowBattery = mBatteryInfo.batteryLevel <= mWarningLevel
+                || (mBatteryInfo.discharging
+                && mBatteryInfo.remainingTimeUs < TimeUnit.HOURS.toMicros(mPolicy.lowBatteryHour));
+
+        int state = BatteryTip.StateType.INVISIBLE;
+        if (mPolicy.lowBatteryEnabled) {
+            if (powerSaveModeOn) {
+                // Show it is handled if battery saver is on
+                state = BatteryTip.StateType.HANDLED;
+            } else if (mPolicy.testLowBatteryTip || (mBatteryInfo.discharging && lowBattery)) {
+                // Show it is new if in test or in discharging low battery state
+                state = BatteryTip.StateType.NEW;
+            }
+        }
+
         return new LowBatteryTip(
-                isShown ? BatteryTip.StateType.NEW : BatteryTip.StateType.INVISIBLE);
+                state, powerSaveModeOn, mBatteryInfo.remainingLabel);
     }
 }
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
index f02dd72..2afdc81 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
@@ -74,10 +74,10 @@
         TIP_ORDER.append(TipType.APP_RESTRICTION, 0);
         TIP_ORDER.append(TipType.BATTERY_SAVER, 1);
         TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 2);
-        TIP_ORDER.append(TipType.SUMMARY, 3);
-        TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 4);
-        TIP_ORDER.append(TipType.REDUCED_BATTERY, 5);
-        TIP_ORDER.append(TipType.LOW_BATTERY, 6);
+        TIP_ORDER.append(TipType.LOW_BATTERY, 3);
+        TIP_ORDER.append(TipType.SUMMARY, 4);
+        TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 5);
+        TIP_ORDER.append(TipType.REDUCED_BATTERY, 6);
         TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 7);
     }
 
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java
index 86237dd..b48a7dd 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java
@@ -25,36 +25,32 @@
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 /**
- * Tip to show current battery life is short
+ * Tip to show current battery level is low or remaining time is less than a certain period
  */
-public class LowBatteryTip extends BatteryTip {
+public class LowBatteryTip extends EarlyWarningTip {
+    private CharSequence mSummary;
 
-    public LowBatteryTip(@StateType int state) {
-        super(TipType.LOW_BATTERY, state, false /* showDialog */);
+    public LowBatteryTip(@StateType int state, boolean powerSaveModeOn, CharSequence summary) {
+        super(state, powerSaveModeOn);
+        mType = TipType.LOW_BATTERY;
+        mSummary = summary;
     }
 
-    private LowBatteryTip(Parcel in) {
+    public LowBatteryTip(Parcel in) {
         super(in);
-    }
-
-    @Override
-    public CharSequence getTitle(Context context) {
-        return context.getString(R.string.battery_tip_low_battery_title);
+        mSummary = in.readCharSequence();
     }
 
     @Override
     public CharSequence getSummary(Context context) {
-        return context.getString(R.string.battery_tip_low_battery_summary);
+        return mState == StateType.HANDLED ? context.getString(
+                R.string.battery_tip_early_heads_up_done_summary) : mSummary;
     }
 
     @Override
-    public int getIconId() {
-        return R.drawable.ic_perm_device_information_red_24dp;
-    }
-
-    @Override
-    public void updateState(BatteryTip tip) {
-        mState = tip.mState;
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeCharSequence(mSummary);
     }
 
     @Override
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
index b50cc40..b0d6a7d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
@@ -48,9 +48,9 @@
             BatteryTip.TipType.APP_RESTRICTION,
             BatteryTip.TipType.BATTERY_SAVER,
             BatteryTip.TipType.HIGH_DEVICE_USAGE,
+            BatteryTip.TipType.LOW_BATTERY,
             BatteryTip.TipType.SUMMARY,
-            BatteryTip.TipType.SMART_BATTERY_MANAGER,
-            BatteryTip.TipType.LOW_BATTERY};
+            BatteryTip.TipType.SMART_BATTERY_MANAGER};
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private BatteryStatsHelper mBatteryStatsHelper;
     @Mock
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java
index 654b247..350326a 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPolicyTest.java
@@ -48,7 +48,8 @@
             + ",excessive_bg_drain_percentage=25"
             + ",test_battery_saver_tip=true"
             + ",test_high_usage_tip=false"
-            + ",test_smart_battery_tip=true";
+            + ",test_smart_battery_tip=true"
+            + ",test_low_battery_tip=true";
     private Context mContext;
 
     @Before
@@ -80,6 +81,7 @@
         assertThat(batteryTipPolicy.testBatterySaverTip).isTrue();
         assertThat(batteryTipPolicy.testHighUsageTip).isFalse();
         assertThat(batteryTipPolicy.testSmartBatteryTip).isTrue();
+        assertThat(batteryTipPolicy.testLowBatteryTip).isTrue();
     }
 
     @Test
@@ -100,11 +102,12 @@
         assertThat(batteryTipPolicy.reducedBatteryEnabled).isFalse();
         assertThat(batteryTipPolicy.reducedBatteryPercent).isEqualTo(50);
         assertThat(batteryTipPolicy.lowBatteryEnabled).isFalse();
-        assertThat(batteryTipPolicy.lowBatteryHour).isEqualTo(16);
+        assertThat(batteryTipPolicy.lowBatteryHour).isEqualTo(3);
         assertThat(batteryTipPolicy.dataHistoryRetainDay).isEqualTo(30);
         assertThat(batteryTipPolicy.excessiveBgDrainPercentage).isEqualTo(10);
         assertThat(batteryTipPolicy.testBatterySaverTip).isFalse();
         assertThat(batteryTipPolicy.testHighUsageTip).isFalse();
         assertThat(batteryTipPolicy.testSmartBatteryTip).isFalse();
+        assertThat(batteryTipPolicy.testLowBatteryTip).isFalse();
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
index 1f4affa..9764559 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
@@ -17,12 +17,15 @@
 package com.android.settings.fuelgauge.batterytip.detectors;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.spy;
 
-import android.text.format.DateUtils;
+import android.content.Context;
+import android.os.PowerManager;
 
 import com.android.settings.fuelgauge.BatteryInfo;
 import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
@@ -31,8 +34,12 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowPowerManager;
 import org.robolectric.util.ReflectionHelpers;
 
+import java.util.concurrent.TimeUnit;
+
 @RunWith(SettingsRobolectricTestRunner.class)
 public class LowBatteryDetectorTest {
 
@@ -40,36 +47,68 @@
     private BatteryInfo mBatteryInfo;
     private BatteryTipPolicy mPolicy;
     private LowBatteryDetector mLowBatteryDetector;
+    private ShadowPowerManager mShadowPowerManager;
+    private Context mContext;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         mPolicy = spy(new BatteryTipPolicy(RuntimeEnvironment.application));
+        mContext = RuntimeEnvironment.application;
+        mShadowPowerManager = Shadows.shadowOf(mContext.getSystemService(PowerManager.class));
         ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", true);
+        ReflectionHelpers.setField(mPolicy, "lowBatteryHour", 3);
+        mBatteryInfo.discharging = true;
 
-        mLowBatteryDetector = new LowBatteryDetector(mPolicy, mBatteryInfo);
+        mLowBatteryDetector = new LowBatteryDetector(mContext, mPolicy, mBatteryInfo);
     }
 
     @Test
     public void testDetect_disabledByPolicy_tipInvisible() {
         ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", false);
+        mShadowPowerManager.setIsPowerSaveMode(true);
 
         assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
     }
 
     @Test
-    public void testDetect_shortBatteryLife_tipVisible() {
-        mBatteryInfo.discharging = true;
-        mBatteryInfo.remainingTimeUs = DateUtils.MINUTE_IN_MILLIS;
+    public void testDetect_enabledByTest_tipNew() {
+        ReflectionHelpers.setField(mPolicy, "testLowBatteryTip", true);
 
-        assertThat(mLowBatteryDetector.detect().isVisible()).isTrue();
+        assertThat(mLowBatteryDetector.detect().getState()).isEqualTo(BatteryTip.StateType.NEW);
     }
 
     @Test
-    public void testDetect_longBatteryLife_tipInvisible() {
-        mBatteryInfo.discharging = true;
-        mBatteryInfo.remainingTimeUs = DateUtils.DAY_IN_MILLIS;
+    public void testDetect_lowBattery_tipNew() {
+        mBatteryInfo.batteryLevel = 3;
+        mBatteryInfo.remainingTimeUs = TimeUnit.DAYS.toMillis(1);
+        assertThat(mLowBatteryDetector.detect().getState()).isEqualTo(BatteryTip.StateType.NEW);
+
+        mBatteryInfo.batteryLevel = 50;
+        mBatteryInfo.remainingTimeUs = TimeUnit.MINUTES.toMillis(1);
+        assertThat(mLowBatteryDetector.detect().getState()).isEqualTo(BatteryTip.StateType.NEW);
+    }
+
+    @Test
+    public void testDetect_batterySaverOn_tipHandled() {
+        mShadowPowerManager.setIsPowerSaveMode(true);
+
+        assertThat(mLowBatteryDetector.detect().getState())
+                .isEqualTo(BatteryTip.StateType.HANDLED);
+    }
+
+    @Test
+    public void testDetect_charging_tipInvisible() {
+        mBatteryInfo.discharging = false;
+
+        assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
+    }
+
+    @Test
+    public void testDetect_noEarlyWarning_tipInvisible() {
+        mBatteryInfo.remainingTimeUs = TimeUnit.DAYS.toMicros(1);
+        mBatteryInfo.batteryLevel = 100;
 
         assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTipTest.java
new file mode 100644
index 0000000..359d260
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTipTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 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.batterytip.tips;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Parcel;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class LowBatteryTipTest {
+
+    private static final CharSequence SUMMARY = "Only 15 minutes left";
+
+    @Mock
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+    private Context mContext;
+    private LowBatteryTip mLowBatteryTip;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mLowBatteryTip = new LowBatteryTip(BatteryTip.StateType.NEW, false /* powerSaveModeOn */,
+                SUMMARY);
+    }
+
+    @Test
+    public void testParcelable() {
+        Parcel parcel = Parcel.obtain();
+        mLowBatteryTip.writeToParcel(parcel, mLowBatteryTip.describeContents());
+        parcel.setDataPosition(0);
+
+        final LowBatteryTip parcelTip = new LowBatteryTip(parcel);
+
+        assertThat(parcelTip.isPowerSaveModeOn()).isFalse();
+        assertThat(parcelTip.getSummary(mContext)).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void getSummary_tipHandled_showSummary() {
+        mLowBatteryTip.mState = BatteryTip.StateType.HANDLED;
+
+        assertThat(mLowBatteryTip.getSummary(mContext)).isEqualTo("Some features may be limited");
+    }
+
+    @Test
+    public void getSummary_tipNew_showSummary() {
+        mLowBatteryTip.mState = BatteryTip.StateType.NEW;
+
+        assertThat(mLowBatteryTip.getSummary(mContext)).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void log_lowBatteryActionWithCorrectState() {
+        mLowBatteryTip.log(mContext, mMetricsFeatureProvider);
+
+        verify(mMetricsFeatureProvider).action(mContext,
+                MetricsProto.MetricsEvent.ACTION_LOW_BATTERY_TIP, BatteryTip.StateType.NEW);
+    }
+}