Add Battery Defender feature to Settings

 - Adding new tips of Battery Defender, will be presented once battery is overheated
 - Launch Help Center article of battery overheat when clicking Battery Defender tip
 Screenshots: https://screenshot.googleplex.com/7jUibTJANgR6UQ6.png
 	      https://screenshot.googleplex.com/tUj2LLi87SfndBN.png

Bug: 172794045
Bug: 173497281
Bug: 173496188
Test: make RunSettingsRoboTests -j40
Merged-In: Ibb106a5d42cdf6232abf9ddf4b3225bdcebccf4a
Change-Id: Id400cec248a87dd27194d93bdf035e43bbcb7f4f
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 138db6a..ff22182 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7193,6 +7193,8 @@
     <string name="help_url_sound" translatable="false"></string>
     <!-- Help URL, Battery [DO NOT TRANSLATE] -->
     <string name="help_url_battery" translatable="false"></string>
+    <!-- Help URL, Battery Defender [DO NOT TRANSLATE] -->
+    <string name="help_url_battery_defender" translatable="false"></string>
     <!-- Help URL, Accounts [DO NOT TRANSLATE] -->
     <string name="help_url_accounts" translatable="false"></string>
     <!-- Help URL, Choose lockscreen [DO NOT TRANSLATE] -->
diff --git a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
index 2041543..71e65bf 100644
--- a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.BatteryManager;
 import android.os.PowerManager;
 import android.util.Log;
 
@@ -39,6 +40,7 @@
  * 1. Battery level(e.g. 100%->99%)
  * 2. Battery status(e.g. plugged->unplugged)
  * 3. Battery saver(e.g. off->on)
+ * 4. Battery health(e.g. good->overheat)
  */
 public class BatteryBroadcastReceiver extends BroadcastReceiver {
 
@@ -49,6 +51,7 @@
      * Battery level(e.g. 100%->99%)
      * Battery status(e.g. plugged->unplugged)
      * Battery saver(e.g. off->on)
+     * Battery health(e.g. good->overheat)
      */
     public interface OnBatteryChangedListener {
         void onBatteryChanged(@BatteryUpdateType int type);
@@ -59,19 +62,23 @@
             BatteryUpdateType.BATTERY_LEVEL,
             BatteryUpdateType.BATTERY_SAVER,
             BatteryUpdateType.BATTERY_STATUS,
+            BatteryUpdateType.BATTERY_HEALTH,
             BatteryUpdateType.BATTERY_NOT_PRESENT})
     public @interface BatteryUpdateType {
         int MANUAL = 0;
         int BATTERY_LEVEL = 1;
         int BATTERY_SAVER = 2;
         int BATTERY_STATUS = 3;
-        int BATTERY_NOT_PRESENT = 4;
+        int BATTERY_HEALTH = 4;
+        int BATTERY_NOT_PRESENT = 5;
     }
 
     @VisibleForTesting
     String mBatteryLevel;
     @VisibleForTesting
     String mBatteryStatus;
+    @VisibleForTesting
+    int mBatteryHealth;
     private OnBatteryChangedListener mBatteryListener;
     private Context mContext;
 
@@ -106,11 +113,15 @@
             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                 final String batteryLevel = Utils.getBatteryPercentage(intent);
                 final String batteryStatus = Utils.getBatteryStatus(mContext, intent);
+                final int batteryHealth = intent.getIntExtra(
+                        BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
                 if (!Utils.isBatteryPresent(intent)) {
                     Log.w(TAG, "Problem reading the battery meter.");
                     mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_NOT_PRESENT);
                 } else if (forceUpdate) {
                     mBatteryListener.onBatteryChanged(BatteryUpdateType.MANUAL);
+                } else if (batteryHealth != mBatteryHealth) {
+                    mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_HEALTH);
                 } else if(!batteryLevel.equals(mBatteryLevel)) {
                     mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL);
                 } else if (!batteryStatus.equals(mBatteryStatus)) {
@@ -118,6 +129,7 @@
                 }
                 mBatteryLevel = batteryLevel;
                 mBatteryStatus = batteryStatus;
+                mBatteryHealth = batteryHealth;
             } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
                 mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_SAVER);
             }
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
index 7b910a1..9066444 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
@@ -124,7 +124,9 @@
     public void updateHeaderPreference(BatteryInfo info) {
         mBatteryPercentText.setText(formatBatteryPercentageText(info.batteryLevel));
         if (!mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info)) {
-            if (info.remainingLabel == null) {
+            if (BatteryUtils.isBatteryDefenderOn(info)) {
+                mSummary1.setText(null);
+            } else if (info.remainingLabel == null) {
                 mSummary1.setText(info.statusLabel);
             } else {
                 mSummary1.setText(info.remainingLabel);
diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java
index 1935c35..49bdcbb 100644
--- a/src/com/android/settings/fuelgauge/BatteryInfo.java
+++ b/src/com/android/settings/fuelgauge/BatteryInfo.java
@@ -45,6 +45,7 @@
     public CharSequence remainingLabel;
     public int batteryLevel;
     public boolean discharging = true;
+    public boolean isOverheated;
     public long remainingTimeUs = 0;
     public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN;
     public String batteryPercentString;
@@ -232,6 +233,9 @@
         info.batteryPercentString = Utils.formatPercentage(info.batteryLevel);
         info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
         info.averageTimeToDischarge = estimate.getAverageDischargeTime();
+        info.isOverheated = batteryBroadcast.getIntExtra(
+                BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN)
+                == BatteryManager.BATTERY_HEALTH_OVERHEAT;
 
         info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast);
         if (!info.mCharging) {
@@ -251,7 +255,12 @@
                 BatteryManager.BATTERY_STATUS_UNKNOWN);
         info.discharging = false;
         info.suggestionLabel = null;
-        if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
+        if (info.isOverheated && status != BatteryManager.BATTERY_STATUS_FULL) {
+            info.remainingLabel = null;
+            int chargingLimitedResId = R.string.power_charging_limited;
+            info.chargeLabel =
+                    context.getString(chargingLimitedResId, info.batteryPercentString);
+        } else if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
             info.remainingTimeUs = chargeTime;
             CharSequence timeString = StringUtil.formatElapsedTime(context,
                     PowerUtil.convertUsToMs(info.remainingTimeUs), false /* withSeconds */);
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index 03387f9..3fc9c5e 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -404,6 +404,13 @@
     }
 
     /**
+     * Return {@code true} if battery is overheated and charging.
+     */
+    public static boolean isBatteryDefenderOn(BatteryInfo batteryInfo) {
+        return batteryInfo.isOverheated && !batteryInfo.discharging;
+    }
+
+    /**
      * Find package uid from package name
      *
      * @param packageName used to find the uid
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
index 8d6e07d..0c916b2 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
@@ -23,6 +23,7 @@
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.settings.fuelgauge.BatteryInfo;
 import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batterytip.detectors.BatteryDefenderDetector;
 import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector;
 import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
 import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
@@ -72,6 +73,7 @@
                 batteryInfo.discharging).detect());
         tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect());
         tips.add(new EarlyWarningDetector(policy, context).detect());
+        tips.add(new BatteryDefenderDetector(batteryInfo).detect());
         tips.add(new SummaryDetector(policy, batteryInfo.averageTimeToDischarge).detect());
         // Disable this feature now since it introduces false positive cases. We will try to improve
         // it in the future.
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
index ed06cce..9ef1070 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
@@ -29,6 +29,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.settings.SettingsActivity;
 import com.android.settings.core.InstrumentedPreferenceFragment;
+import com.android.settings.fuelgauge.batterytip.actions.BatteryDefenderAction;
 import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
 import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction;
 import com.android.settings.fuelgauge.batterytip.actions.OpenBatterySaverAction;
@@ -111,6 +112,8 @@
                 }
             case BatteryTip.TipType.REMOVE_APP_RESTRICTION:
                 return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip);
+            case BatteryTip.TipType.BATTERY_DEFENDER:
+                return new BatteryDefenderAction(settingsActivity);
             default:
                 return null;
         }
diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java
new file mode 100644
index 0000000..b8f5483
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.actions;
+
+import android.content.Intent;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settingslib.HelpUtils;
+
+/**
+ * Action to open the Support Center article
+ */
+public class BatteryDefenderAction extends BatteryTipAction {
+    private SettingsActivity mSettingsActivity;
+
+    public BatteryDefenderAction(SettingsActivity settingsActivity) {
+        super(settingsActivity.getApplicationContext());
+        mSettingsActivity = settingsActivity;
+    }
+
+    /**
+     * Handle the action when user clicks positive button
+     */
+    @Override
+    public void handlePositiveAction(int metricsKey) {
+        final Intent intent = HelpUtils.getHelpIntent(
+                mContext,
+                mContext.getString(R.string.help_url_battery_defender),
+                getClass().getName());
+        if (intent != null) {
+            mSettingsActivity.startActivityForResult(intent, 0);
+        }
+        // TODO(b/173985153): Add logging enums for Battery Defender.
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java
new file mode 100644
index 0000000..86744f5
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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.detectors;
+
+import com.android.settings.fuelgauge.BatteryInfo;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryDefenderTip;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+
+/**
+ * Detect whether the battery is overheated
+ */
+public class BatteryDefenderDetector implements BatteryTipDetector {
+    private BatteryInfo mBatteryInfo;
+
+    public BatteryDefenderDetector(BatteryInfo batteryInfo) {
+        mBatteryInfo = batteryInfo;
+    }
+
+    @Override
+    public BatteryTip detect() {
+        final int state =
+                BatteryUtils.isBatteryDefenderOn(mBatteryInfo)
+                        ? BatteryTip.StateType.NEW
+                        : BatteryTip.StateType.INVISIBLE;
+        return new BatteryDefenderTip(state);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
new file mode 100644
index 0000000..cd23e50
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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 android.content.Context;
+import android.os.Parcel;
+
+import com.android.settings.R;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+/**
+ * Tip to show current battery is overheated
+ */
+public class BatteryDefenderTip extends BatteryTip {
+
+    public BatteryDefenderTip(@StateType int state) {
+        super(TipType.BATTERY_DEFENDER, state, false /* showDialog */);
+    }
+
+    private BatteryDefenderTip(Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public CharSequence getTitle(Context context) {
+        return context.getString(R.string.battery_tip_limited_temporarily_title);
+    }
+
+    @Override
+    public CharSequence getSummary(Context context) {
+        return context.getString(R.string.battery_tip_limited_temporarily_summary);
+    }
+
+    @Override
+    public int getIconId() {
+        return R.drawable.ic_battery_status_good_24dp;
+    }
+
+    @Override
+    public void updateState(BatteryTip tip) {
+        mState = tip.mState;
+    }
+
+    @Override
+    public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
+        // TODO(b/173985153): Add logging enums for Battery Defender.
+    }
+
+    public static final Creator CREATOR = new Creator() {
+        public BatteryTip createFromParcel(Parcel in) {
+            return new BatteryDefenderTip(in);
+        }
+
+        public BatteryTip[] newArray(int size) {
+            return new BatteryDefenderTip[size];
+        }
+    };
+
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
index ebc4939..13015a7 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
@@ -17,7 +17,6 @@
 package com.android.settings.fuelgauge.batterytip.tips;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseIntArray;
@@ -58,7 +57,8 @@
             TipType.APP_RESTRICTION,
             TipType.REDUCED_BATTERY,
             TipType.LOW_BATTERY,
-            TipType.REMOVE_APP_RESTRICTION})
+            TipType.REMOVE_APP_RESTRICTION,
+            TipType.BATTERY_DEFENDER})
     public @interface TipType {
         int SMART_BATTERY_MANAGER = 0;
         int APP_RESTRICTION = 1;
@@ -68,20 +68,22 @@
         int LOW_BATTERY = 5;
         int SUMMARY = 6;
         int REMOVE_APP_RESTRICTION = 7;
+        int BATTERY_DEFENDER = 8;
     }
 
     @VisibleForTesting
     static final SparseIntArray TIP_ORDER;
     static {
         TIP_ORDER = new SparseIntArray();
-        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.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);
+        TIP_ORDER.append(TipType.BATTERY_DEFENDER, 0);
+        TIP_ORDER.append(TipType.APP_RESTRICTION, 1);
+        TIP_ORDER.append(TipType.BATTERY_SAVER, 2);
+        TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 3);
+        TIP_ORDER.append(TipType.LOW_BATTERY, 4);
+        TIP_ORDER.append(TipType.SUMMARY, 5);
+        TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 6);
+        TIP_ORDER.append(TipType.REDUCED_BATTERY, 7);
+        TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 8);
     }
 
     private static final String KEY_PREFIX = "key_battery_tip";
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
index 742daf2..a072988 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
@@ -66,6 +66,7 @@
         mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
         mBatteryBroadcastReceiver.mBatteryLevel = BATTERY_INIT_LEVEL;
         mBatteryBroadcastReceiver.mBatteryStatus = BATTERY_INIT_STATUS;
+        mBatteryBroadcastReceiver.mBatteryHealth = BatteryManager.BATTERY_HEALTH_UNKNOWN;
         mBatteryBroadcastReceiver.setBatteryChangedListener(mBatteryListener);
 
         mChargingIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
@@ -95,6 +96,21 @@
             BatteryFixSliceTest.ShadowBatteryStatsHelperLoader.class,
             BatteryFixSliceTest.ShadowBatteryTipLoader.class
     })
+    public void testOnReceive_batteryHealthChanged_dataUpdated() {
+        mChargingIntent
+                .putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+        mBatteryBroadcastReceiver.onReceive(mContext, mChargingIntent);
+
+        assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
+                .isEqualTo(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+        verify(mBatteryListener).onBatteryChanged(BatteryUpdateType.BATTERY_HEALTH);
+    }
+
+    @Test
+    @Config(shadows = {
+            BatteryFixSliceTest.ShadowBatteryStatsHelperLoader.class,
+            BatteryFixSliceTest.ShadowBatteryTipLoader.class
+    })
     public void onReceive_batteryNotPresent_shouldShowHelpMessage() {
         mChargingIntent.putExtra(BatteryManager.EXTRA_PRESENT, false);
 
@@ -131,6 +147,8 @@
 
         assertThat(mBatteryBroadcastReceiver.mBatteryLevel).isEqualTo(batteryLevel);
         assertThat(mBatteryBroadcastReceiver.mBatteryStatus).isEqualTo(batteryStatus);
+        assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
+                .isEqualTo(BatteryManager.BATTERY_HEALTH_UNKNOWN);
         verify(mBatteryListener, never()).onBatteryChanged(anyInt());
     }
 
@@ -149,6 +167,8 @@
                 .isEqualTo(Utils.getBatteryPercentage(mChargingIntent));
         assertThat(mBatteryBroadcastReceiver.mBatteryStatus)
                 .isEqualTo(Utils.getBatteryStatus(mContext, mChargingIntent));
+        assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
+                .isEqualTo(BatteryManager.BATTERY_HEALTH_UNKNOWN);
         // 2 times because register will force update the battery
         verify(mBatteryListener, times(2)).onBatteryChanged(BatteryUpdateType.MANUAL);
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
index a6cf653..ac3c8f9 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
@@ -176,6 +176,15 @@
     }
 
     @Test
+    public void updatePreference_isOverheat_showEmptyText() {
+        mBatteryInfo.isOverheated = true;
+
+        mController.updateHeaderPreference(mBatteryInfo);
+
+        assertThat(mSummary.getText().toString().isEmpty()).isTrue();
+    }
+
+    @Test
     public void onStart_shouldStyleActionBar() {
         when(mEntityHeaderController.setRecyclerView(nullable(RecyclerView.class), eq(mLifecycle)))
                 .thenReturn(mEntityHeaderController);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
index 3cbc58a..d0af982 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
@@ -246,6 +246,22 @@
         assertThat(info.chargeLabel).isEqualTo("100%");
     }
 
+    @Test
+    public void testGetBatteryInfo_chargingWithOverheated_updateChargeLabel() {
+        doReturn(TEST_CHARGE_TIME_REMAINING)
+            .when(mBatteryStats)
+            .computeChargeTimeRemaining(anyLong());
+        mChargingBatteryBroadcast
+                .putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+        BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
+                mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000,
+                false /* shortString */);
+
+        assertThat(info.isOverheated).isTrue();
+        assertThat(info.chargeLabel).isEqualTo("50% - Battery limited temporarily");
+    }
+
     // Make our battery stats return a sequence of battery events.
     private void mockBatteryStatsHistory() {
         // Mock out new data every time start...Locked is called.
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
index 0faddc3..5d985fd 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
@@ -144,6 +144,8 @@
     @Mock
     private BatterySipper mIdleBatterySipper;
     @Mock
+    private BatteryInfo mBatteryInfo;
+    @Mock
     private Bundle mBundle;
     @Mock
     private UserManager mUserManager;
@@ -754,4 +756,36 @@
         assertThat(estimate.isBasedOnUsage()).isTrue();
         assertThat(estimate.getAverageDischargeTime()).isEqualTo(1000);
     }
+
+    @Test
+    public void testIsBatteryDefenderOn_isOverheatedAndIsCharging_returnTrue() {
+        mBatteryInfo.isOverheated = true;
+        mBatteryInfo.discharging = false;
+
+        assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isTrue();
+    }
+
+    @Test
+    public void testIsBatteryDefenderOn_isOverheatedAndDischarging_returnFalse() {
+        mBatteryInfo.isOverheated = true;
+        mBatteryInfo.discharging = true;
+
+        assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isFalse();
+    }
+
+    @Test
+    public void testIsBatteryDefenderOn_notOverheatedAndDischarging_returnFalse() {
+        mBatteryInfo.isOverheated = false;
+        mBatteryInfo.discharging = true;
+
+        assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isFalse();
+    }
+
+    @Test
+    public void testIsBatteryDefenderOn_notOverheatedAndIsCharging_returnFalse() {
+        mBatteryInfo.isOverheated = false;
+        mBatteryInfo.discharging = false;
+
+        assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isFalse();
+    }
 }
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 116033b..8cc17d8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
@@ -50,6 +50,7 @@
 public class BatteryTipLoaderTest {
 
     private static final int[] TIP_ORDER = {
+            BatteryTip.TipType.BATTERY_DEFENDER,
             BatteryTip.TipType.BATTERY_SAVER,
             BatteryTip.TipType.HIGH_DEVICE_USAGE,
             BatteryTip.TipType.LOW_BATTERY,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java
index 275bfe0..6199788 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java
@@ -23,10 +23,12 @@
 
 import com.android.settings.SettingsActivity;
 import com.android.settings.core.InstrumentedPreferenceFragment;
+import com.android.settings.fuelgauge.batterytip.actions.BatteryDefenderAction;
 import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
 import com.android.settings.fuelgauge.batterytip.actions.OpenBatterySaverAction;
 import com.android.settings.fuelgauge.batterytip.actions.OpenRestrictAppFragmentAction;
 import com.android.settings.fuelgauge.batterytip.actions.RestrictAppAction;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryDefenderTip;
 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
 import com.android.settings.fuelgauge.batterytip.tips.EarlyWarningTip;
 import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
@@ -53,6 +55,7 @@
     private RestrictAppTip mRestrictAppTip;
     private EarlyWarningTip mEarlyWarningTip;
     private LowBatteryTip mLowBatteryTip;
+    private BatteryDefenderTip mBatteryDefenderTip;
 
     @Before
     public void setUp() {
@@ -67,6 +70,7 @@
         mLowBatteryTip = spy(
                 new LowBatteryTip(BatteryTip.StateType.NEW, false /* powerSaveModeOn */,
                         "" /* summary */));
+        mBatteryDefenderTip = spy(new BatteryDefenderTip(BatteryTip.StateType.NEW));
     }
 
     @Test
@@ -116,4 +120,13 @@
         assertThat(BatteryTipUtils.getActionForBatteryTip(mLowBatteryTip, mSettingsActivity,
                 mFragment)).isInstanceOf(OpenBatterySaverAction.class);
     }
+
+    @Test
+    public void
+            testGetActionForBatteryTip_typeBatteryDefenderStateNew_returnActionBatteryDefender() {
+        when(mBatteryDefenderTip.getState()).thenReturn(BatteryTip.StateType.NEW);
+
+        assertThat(BatteryTipUtils.getActionForBatteryTip(mBatteryDefenderTip, mSettingsActivity,
+                mFragment)).isInstanceOf(BatteryDefenderAction.class);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java
new file mode 100644
index 0000000..a1f9d1f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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.detectors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.fuelgauge.BatteryInfo;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+
+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;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatteryDefenderDetectorTest {
+
+    @Mock
+    private BatteryInfo mBatteryInfo;
+    private BatteryDefenderDetector mBatteryDefenderDetector;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mBatteryInfo.discharging = false;
+
+        mBatteryDefenderDetector = new BatteryDefenderDetector(mBatteryInfo);
+    }
+
+    @Test
+    public void testDetect_notOverheated_tipInvisible() {
+        mBatteryInfo.isOverheated = false;
+
+        assertThat(mBatteryDefenderDetector.detect().isVisible()).isFalse();
+    }
+
+    @Test
+    public void testDetect_isOverheated_tipNew() {
+        mBatteryInfo.isOverheated = true;
+
+        assertThat(mBatteryDefenderDetector.detect().getState())
+                .isEqualTo(BatteryTip.StateType.NEW);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
new file mode 100644
index 0000000..c6eb15d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 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 android.content.Context;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatteryDefenderTipTest {
+
+    private Context mContext;
+    private BatteryDefenderTip mBatteryDefenderTip;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mBatteryDefenderTip = new BatteryDefenderTip(BatteryTip.StateType.NEW);
+    }
+
+    @Test
+    public void getTitle_showTitle() {
+        assertThat(mBatteryDefenderTip.getTitle(mContext))
+                .isEqualTo(mContext.getString(R.string.battery_tip_limited_temporarily_title));
+    }
+
+    @Test
+    public void getSummary_showSummary() {
+        assertThat(mBatteryDefenderTip.getSummary(mContext))
+                .isEqualTo(mContext.getString(R.string.battery_tip_limited_temporarily_summary));
+    }
+
+    @Test
+    public void getIcon_showIcon() {
+        assertThat(mBatteryDefenderTip.getIconId())
+                .isEqualTo(R.drawable.ic_battery_status_good_24dp);
+    }
+}