Implements the buttons layout for the extra defend

Bug: 235246949
Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.fuelgauge.*
Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.widget.CardPreferenceTest
Test: manual test
Change-Id: I1dc4ab31adf85c684a4c09bd6c9bcfb54b52dc3c
diff --git a/res/layout/card_preference.xml b/res/layout/card_preference.xml
index be49ca3..39523eb 100644
--- a/res/layout/card_preference.xml
+++ b/res/layout/card_preference.xml
@@ -57,6 +57,30 @@
             android:maxLines="10"
             style="@style/PreferenceSummaryTextStyle"/>
 
+        <RelativeLayout
+            android:id="@+id/card_preference_buttons"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:layout_below="@android:id/summary"
+            android:visibility="gone">
+            <Button
+                android:id="@android:id/button1"
+                style="@style/CardPreferencePrimaryButton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginHorizontal="4dp"
+                android:layout_toStartOf="@android:id/button2"
+                android:visibility="gone"/>
+            <Button
+                android:id="@android:id/button2"
+                style="@style/CardPreferenceBorderlessButton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginHorizontal="4dp"
+                android:layout_alignParentEnd="true"
+                android:visibility="gone"/>
+        </RelativeLayout>
     </RelativeLayout>
 
     <!-- Preference should place its actual preference widget here. -->
diff --git a/res/values/config.xml b/res/values/config.xml
index 8b255e6..4180154 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -622,6 +622,9 @@
     <!-- Whether to put the apps with system UID into system component bucket or not -->
     <bool name="config_battery_combine_system_components">false</bool>
 
+    <!-- The extra value for battery tip -->
+    <integer name="config_battery_extra_tip_value">12</integer>
+
     <!-- Whether to enable the advanced vpn feature. The default is not to. -->
     <bool name="config_advanced_vpn_enabled">false</bool>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7cfb966..8fbe72c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6281,6 +6281,8 @@
     <string name="battery_tip_limited_temporarily_title">Charging temporarily limited</string>
     <!-- Summary for the battery limited temporarily tip [CHAR LIMIT=NONE] -->
     <string name="battery_tip_limited_temporarily_summary">To preserve your battery. Learn more.</string>
+    <!-- Summary for the battery limited temporarily extra tip [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_limited_temporarily_extra_summary"><xliff:g id="percent" example="10%">%1$s</xliff:g></string>
     <!-- Text of battery limited temporarily tip resume charge button. [CHAR LIMIT=NONE] -->
     <string name="battery_tip_limited_temporarily_dialog_resume_charge">Resume charging</string>
     <!-- Message of battery limited temporarily tip. [CHAR LIMIT=NONE] -->
@@ -6337,6 +6339,8 @@
     <string name="battery_tip_unrestrict_app_dialog_ok">Remove</string>
     <!-- CANCEL button for dialog to remove restriction for app [CHAR LIMIT=NONE] -->
     <string name="battery_tip_unrestrict_app_dialog_cancel">Cancel</string>
+    <!-- Charge to full button for battery defender tips [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_charge_to_full_button">Charge to full</string>
 
     <!-- Message for battery tip dialog to show the battery summary -->
     <string name="battery_tip_dialog_summary_message" product="default">Your apps are using a normal amount of battery. If apps use too much battery, your phone will suggest actions you can take.\n\nYou can always turn on Battery Saver if you\u2019re running low on battery.</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f147ce9..8dee082 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -969,4 +969,19 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textDirection">locale</item>
     </style>
+
+    <style name="CardPreferencePrimaryButton" parent="@style/ActionPrimaryButton">
+        <item name="android:fontFamily">google-sans-medium</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
+    <style name="CardPreferenceBorderlessButton"
+        parent="@style/Widget.AppCompat.Button.Borderless.Colored">
+        <item name="android:fontFamily">google-sans-medium</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:singleLine">true</item>
+    </style>
 </resources>
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index 83d7a33..94a93b8 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -140,6 +140,11 @@
     boolean isAdaptiveChargingSupported();
 
     /**
+     * Returns {@code true} if current defender mode is extra defend
+     */
+    boolean isExtraDefend();
+
+    /**
      * Gets a intent for one time bypass charge limited to resume charging.
      */
     Intent getResumeChargeIntent();
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 1262641..0adfc9d 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -161,6 +161,11 @@
     }
 
     @Override
+    public boolean isExtraDefend() {
+        return false;
+    }
+
+    @Override
     public Map<Long, Map<String, BatteryHistEntry>> getBatteryHistory(Context context) {
         return null;
     }
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
index 4b98587..ea493a3 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
@@ -31,6 +31,7 @@
 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
 import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
 import com.android.settings.fuelgauge.batterytip.tips.SummaryTip;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.fuelgauge.EstimateKt;
 import com.android.settingslib.utils.AsyncLoaderCompat;
 
@@ -66,13 +67,16 @@
         final BatteryTipPolicy policy = new BatteryTipPolicy(getContext());
         final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(TAG);
         final Context context = getContext();
+        final boolean extraDefend = FeatureFactory.getFactory(context)
+                .getPowerUsageFeatureProvider(context)
+                .isExtraDefend();
 
         tips.add(new LowBatteryDetector(context, policy, batteryInfo).detect());
         tips.add(new HighUsageDetector(context, policy, mBatteryUsageStats, batteryInfo).detect());
         tips.add(new SmartBatteryDetector(
                 context, policy, batteryInfo, context.getContentResolver()).detect());
         tips.add(new EarlyWarningDetector(policy, context).detect());
-        tips.add(new BatteryDefenderDetector(batteryInfo).detect());
+        tips.add(new BatteryDefenderDetector(batteryInfo, extraDefend).detect());
         Collections.sort(tips);
         return tips;
     }
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java
index 5befa33..367b2b1 100644
--- a/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java
@@ -24,10 +24,12 @@
  * Detect whether the battery is overheated
  */
 public class BatteryDefenderDetector implements BatteryTipDetector {
-    private BatteryInfo mBatteryInfo;
+    private final BatteryInfo mBatteryInfo;
+    private final boolean mExtraDefend;
 
-    public BatteryDefenderDetector(BatteryInfo batteryInfo) {
+    public BatteryDefenderDetector(BatteryInfo batteryInfo, boolean extraDefend) {
         mBatteryInfo = batteryInfo;
+        mExtraDefend = extraDefend;
     }
 
     @Override
@@ -36,6 +38,6 @@
                 mBatteryInfo.isOverheated
                     ? BatteryTip.StateType.NEW
                     : BatteryTip.StateType.INVISIBLE;
-        return new BatteryDefenderTip(state);
+        return new BatteryDefenderTip(state, mExtraDefend);
     }
 }
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
index a2890ad..0a133bb 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
@@ -18,18 +18,36 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
 import android.os.Parcel;
+import android.util.Log;
+
+import androidx.preference.Preference;
 
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.CardPreference;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
+import java.text.NumberFormat;
+
 /**
  * Tip to show current battery is overheated
  */
 public class BatteryDefenderTip extends BatteryTip {
 
+    private static final String TAG = "BatteryDefenderTip";
+    private boolean mExtraDefend = false;
+
     public BatteryDefenderTip(@StateType int state) {
+        this(state, false);
+    }
+
+    public BatteryDefenderTip(@StateType int state, boolean extraDefend) {
         super(TipType.BATTERY_DEFENDER, state, true /* showDialog */);
+        mExtraDefend = extraDefend;
     }
 
     private BatteryDefenderTip(Parcel in) {
@@ -43,6 +61,14 @@
 
     @Override
     public CharSequence getSummary(Context context) {
+        if (mExtraDefend) {
+            final int extraValue = context.getResources()
+                    .getInteger(R.integer.config_battery_extra_tip_value);
+            final String extraPercentage = NumberFormat.getPercentInstance()
+                    .format(extraValue * 0.01f);
+            return context.getString(
+                    R.string.battery_tip_limited_temporarily_extra_summary, extraPercentage);
+        }
         return context.getString(R.string.battery_tip_limited_temporarily_summary);
     }
 
@@ -62,6 +88,55 @@
                 mState);
     }
 
+    @Override
+    public void updatePreference(Preference preference) {
+        super.updatePreference(preference);
+        final Context context = preference.getContext();
+
+        CardPreference cardPreference = castToCardPreferenceSafely(preference);
+        if (cardPreference == null) {
+            Log.e(TAG, "cast Preference to CardPreference failed");
+            return;
+        }
+
+        cardPreference.setPrimaryButtonText(
+                context.getString(R.string.battery_tip_charge_to_full_button));
+        cardPreference.setPrimaryButtonClickListener(
+                unused -> {
+                    resumeCharging(context);
+                    preference.setVisible(false);
+                });
+        cardPreference.setPrimaryButtonVisible(isPluggedIn(context));
+
+        cardPreference.setSecondaryButtonText(context.getString(R.string.see_more));
+        cardPreference.setSecondaryButtonClickListener(unused -> cardPreference.performClick());
+        cardPreference.setSecondaryButtonVisible(true);
+    }
+
+    private CardPreference castToCardPreferenceSafely(Preference preference) {
+        return preference instanceof CardPreference ? (CardPreference) preference : null;
+    }
+
+    private void resumeCharging(Context context) {
+        final Intent intent =
+                FeatureFactory.getFactory(context)
+                        .getPowerUsageFeatureProvider(context)
+                        .getResumeChargeIntent();
+        if (intent != null) {
+            context.sendBroadcast(intent);
+        }
+
+        Log.i(TAG, "send resume charging broadcast intent=" + intent);
+    }
+
+    private boolean isPluggedIn(Context context) {
+        final Intent batteryIntent =
+                context.registerReceiver(
+                        /* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        return batteryIntent != null
+                && batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
+    }
+
     public static final Creator CREATOR = new Creator() {
         public BatteryTip createFromParcel(Parcel in) {
             return new BatteryDefenderTip(in);
diff --git a/src/com/android/settings/widget/CardPreference.java b/src/com/android/settings/widget/CardPreference.java
index c041552..afccfac 100644
--- a/src/com/android/settings/widget/CardPreference.java
+++ b/src/com/android/settings/widget/CardPreference.java
@@ -18,18 +18,36 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
 
 import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
 
 import com.google.android.material.card.MaterialCardView;
 
+import java.util.Optional;
+
 /**
  * Preference that wrapped by {@link MaterialCardView}, only support to set icon, title and summary
  */
 public class CardPreference extends Preference {
 
+    private View.OnClickListener mPrimaryBtnClickListener = null;
+    private View.OnClickListener mSecondaryBtnClickListener = null;
+
+    private String mPrimaryButtonText = null;
+    private String mSecondaryButtonText = null;
+
+    private Optional<Button> mPrimaryButton = Optional.empty();
+    private Optional<Button> mSecondaryButton = Optional.empty();
+    private Optional<View> mButtonsGroup = Optional.empty();
+
+    private boolean mPrimaryButtonVisible = false;
+    private boolean mSecondaryButtonVisible = false;
+
     public CardPreference(Context context) {
         this(context, null /* attrs */);
     }
@@ -37,4 +55,94 @@
     public CardPreference(Context context, AttributeSet attrs) {
         super(context, attrs, R.attr.cardPreferenceStyle);
     }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        initButtonsAndLayout(holder);
+    }
+
+    private void initButtonsAndLayout(PreferenceViewHolder holder) {
+        mPrimaryButton = Optional.ofNullable((Button) holder.findViewById(android.R.id.button1));
+        mSecondaryButton = Optional.ofNullable((Button) holder.findViewById(android.R.id.button2));
+        mButtonsGroup = Optional.ofNullable(holder.findViewById(R.id.card_preference_buttons));
+
+        setPrimaryButtonText(mPrimaryButtonText);
+        setPrimaryButtonClickListener(mPrimaryBtnClickListener);
+        setPrimaryButtonVisible(mPrimaryButtonVisible);
+        setSecondaryButtonText(mSecondaryButtonText);
+        setSecondaryButtonClickListener(mSecondaryBtnClickListener);
+        setSecondaryButtonVisible(mSecondaryButtonVisible);
+    }
+
+    /**
+     * Register a callback to be invoked when the primary button is clicked.
+     *
+     * @param l the callback that will run
+     */
+    public void setPrimaryButtonClickListener(View.OnClickListener l) {
+        mPrimaryButton.ifPresent(button -> button.setOnClickListener(l));
+        mPrimaryBtnClickListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when the secondary button is clicked.
+     *
+     * @param l the callback that will run
+     */
+    public void setSecondaryButtonClickListener(View.OnClickListener l) {
+        mSecondaryButton.ifPresent(button -> button.setOnClickListener(l));
+        mSecondaryBtnClickListener = l;
+    }
+
+    /**
+     * Sets the text to be displayed on primary button.
+     *
+     * @param text text to be displayed
+     */
+    public void setPrimaryButtonText(String text) {
+        mPrimaryButton.ifPresent(button -> button.setText(text));
+        mPrimaryButtonText = text;
+    }
+
+    /**
+     * Sets the text to be displayed on secondary button.
+     *
+     * @param text text to be displayed
+     */
+    public void setSecondaryButtonText(String text) {
+        mSecondaryButton.ifPresent(button -> button.setText(text));
+        mSecondaryButtonText = text;
+    }
+
+    /**
+     * Set the visible on the primary button.
+     *
+     * @param visible {@code true} for visible
+     */
+    public void setPrimaryButtonVisible(boolean visible) {
+        mPrimaryButton.ifPresent(
+                button -> button.setVisibility(visible ? View.VISIBLE : View.GONE));
+        mPrimaryButtonVisible = visible;
+        updateButtonGroupsVisibility();
+    }
+
+    /**
+     * Set the visible on the secondary button.
+     *
+     * @param visible {@code true} for visible
+     */
+    public void setSecondaryButtonVisible(boolean visible) {
+        mSecondaryButton.ifPresent(
+                button -> button.setVisibility(visible ? View.VISIBLE : View.GONE));
+        mSecondaryButtonVisible = visible;
+        updateButtonGroupsVisibility();
+    }
+
+    private void updateButtonGroupsVisibility() {
+        int visibility =
+                (mPrimaryButtonVisible || mSecondaryButtonVisible) ? View.VISIBLE : View.GONE;
+        mButtonsGroup.ifPresent(group -> group.setVisibility(visibility));
+    }
 }
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
index a1f9d1f..3309f59 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java
@@ -41,7 +41,8 @@
 
         mBatteryInfo.discharging = false;
 
-        mBatteryDefenderDetector = new BatteryDefenderDetector(mBatteryInfo);
+        mBatteryDefenderDetector = new BatteryDefenderDetector(
+            mBatteryInfo, /* extraDefend= */ false);
     }
 
     @Test
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
index 8660c79..c1ec7c6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
@@ -17,13 +17,25 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.util.Log;
+
+import androidx.preference.Preference;
 
 import com.android.settings.R;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.widget.CardPreference;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 import org.junit.Before;
@@ -33,6 +45,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowLog;
 
 @RunWith(RobolectricTestRunner.class)
 public class BatteryDefenderTipTest {
@@ -43,6 +56,8 @@
     private MetricsFeatureProvider mMetricsFeatureProvider;
 
     @Mock private BatteryTip mBatteryTip;
+    @Mock private Preference mPreference;
+    @Mock private CardPreference mCardPreference;
 
     @Before
     public void setUp() {
@@ -52,6 +67,9 @@
         mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
         mContext = RuntimeEnvironment.application;
         mBatteryDefenderTip = new BatteryDefenderTip(BatteryTip.StateType.NEW);
+
+        when(mPreference.getContext()).thenReturn(mContext);
+        when(mCardPreference.getContext()).thenReturn(mContext);
     }
 
     @Test
@@ -61,12 +79,20 @@
     }
 
     @Test
-    public void getSummary_showSummary() {
+    public void getSummary_notExtraDefended_showNonExtraDefendedSummary() {
         assertThat(mBatteryDefenderTip.getSummary(mContext))
                 .isEqualTo(mContext.getString(R.string.battery_tip_limited_temporarily_summary));
     }
 
     @Test
+    public void getSummary_extraDefended_showExtraDefendedSummary() {
+        BatteryDefenderTip defenderTip = new BatteryDefenderTip(
+                BatteryTip.StateType.NEW, /* extraDefended= */ true);
+
+        assertThat(defenderTip.getSummary(mContext).toString()).isEqualTo("12%");
+    }
+
+    @Test
     public void getIcon_showIcon() {
         assertThat(mBatteryDefenderTip.getIconId())
                 .isEqualTo(R.drawable.ic_battery_status_good_24dp);
@@ -80,4 +106,94 @@
         verify(mMetricsFeatureProvider).action(mContext,
                 SettingsEnums.ACTION_BATTERY_DEFENDER_TIP, mBatteryTip.mState);
     }
+
+    @Test
+    public void updatePreference_castFail_logErrorMessage() {
+        mBatteryDefenderTip.updatePreference(mPreference);
+
+        assertThat(getLastErrorLog()).isEqualTo("cast Preference to CardPreference failed");
+    }
+
+    @Test
+    public void updatePreference_shouldSetPrimaryButtonText() {
+        String expectedText = mContext.getString(R.string.battery_tip_charge_to_full_button);
+
+        mBatteryDefenderTip.updatePreference(mCardPreference);
+
+        verify(mCardPreference).setPrimaryButtonText(expectedText);
+    }
+
+    @Test
+    public void updatePreference_shouldSetSecondaryButtonText() {
+        String expected = mContext.getString(R.string.see_more);
+
+        mBatteryDefenderTip.updatePreference(mCardPreference);
+
+        verify(mCardPreference).setSecondaryButtonText(expected);
+    }
+
+    @Test
+    public void updatePreference_shouldSetSecondaryButtonVisible() {
+        mBatteryDefenderTip.updatePreference(mCardPreference);
+
+        verify(mCardPreference).setSecondaryButtonVisible(true);
+    }
+
+    @Test
+    public void updatePreference_whenCharging_setPrimaryButtonVisibleToBeTrue() {
+        fakeDeviceIsCharging(true);
+
+        mBatteryDefenderTip.updatePreference(mCardPreference);
+
+        verify(mCardPreference).setPrimaryButtonVisible(true);
+    }
+
+    @Test
+    public void updatePreference_whenNotCharging_setPrimaryButtonVisibleToBeFalse() {
+        fakeDeviceIsCharging(false);
+
+        mBatteryDefenderTip.updatePreference(mCardPreference);
+
+        verify(mCardPreference).setPrimaryButtonVisible(false);
+    }
+
+    @Test
+    public void updatePreference_whenGetChargingStatusFailed_setPrimaryButtonVisibleToBeFalse() {
+        fakeGetChargingStatusFailed();
+
+        mBatteryDefenderTip.updatePreference(mCardPreference);
+
+        verify(mCardPreference).setPrimaryButtonVisible(false);
+    }
+
+    private void fakeDeviceIsCharging(boolean charging) {
+        int charged = charging ? 1 : 0; // 1 means charging, 0:not charging
+        Intent batteryChangedIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        batteryChangedIntent.putExtra(BatteryManager.EXTRA_PLUGGED, charged);
+
+        Context mockContext = mock(Context.class);
+        when(mockContext.getString(anyInt())).thenReturn("fake_string");
+        when(mCardPreference.getContext()).thenReturn(mockContext);
+        when(mockContext.registerReceiver(eq(null), any(IntentFilter.class)))
+                .thenReturn(batteryChangedIntent);
+    }
+
+    private void fakeGetChargingStatusFailed() {
+        Context mockContext = mock(Context.class);
+        when(mockContext.getString(anyInt())).thenReturn("fake_string");
+        when(mCardPreference.getContext()).thenReturn(mockContext);
+        when(mockContext.registerReceiver(eq(null), any(IntentFilter.class))).thenReturn(null);
+    }
+
+    private String getLastErrorLog() {
+        return ShadowLog.getLogsForTag(BatteryDefenderTip.class.getSimpleName()).stream()
+                .filter(log -> log.type == Log.ERROR)
+                .reduce((first, second) -> second)
+                .orElse(createErrorLog("No Error Log"))
+                .msg;
+    }
+
+    private ShadowLog.LogItem createErrorLog(String msg) {
+        return new ShadowLog.LogItem(Log.ERROR, "tag", msg, null);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/widget/CardPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/CardPreferenceTest.java
index 85ab609..eba447b 100644
--- a/tests/robotests/src/com/android/settings/widget/CardPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/widget/CardPreferenceTest.java
@@ -16,9 +16,18 @@
 
 package com.android.settings.widget;
 
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.Context;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
 
@@ -26,23 +35,301 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 
 @RunWith(RobolectricTestRunner.class)
 public class CardPreferenceTest {
 
-    private Context mContext;
     private CardPreference mCardPreference;
+    private PreferenceViewHolder mHolder;
 
     @Before
     public void setUp() {
-        mContext = RuntimeEnvironment.application;
-        mContext.setTheme(R.style.SettingsPreferenceTheme);
-        mCardPreference = new CardPreference(mContext);
+        Context context = ApplicationProvider.getApplicationContext();
+        context.setTheme(R.style.Theme_Settings);
+        mCardPreference = new CardPreference(context);
+
+        View rootView = View.inflate(context, R.layout.card_preference_layout, /* parent= */ null);
+        mHolder = PreferenceViewHolder.createInstanceForTests(rootView);
     }
 
     @Test
-    public void getLayoutResource() {
-        assertThat(mCardPreference.getLayoutResource()).isEqualTo(R.layout.card_preference_layout);
+    public void newACardPreference_layoutResourceShouldBeCardPreferenceLayout() {
+        Context context = ApplicationProvider.getApplicationContext();
+        context.setTheme(R.style.SettingsPreferenceTheme);
+
+        CardPreference cardPreference = new CardPreference(context);
+
+        assertThat(cardPreference.getLayoutResource()).isEqualTo(R.layout.card_preference_layout);
+    }
+
+    @Test
+    public void onBindViewHolder_noButtonVisible_buttonsLayoutShouldBeGone() {
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
+    }
+
+    @Test
+    public void onBindViewHolder_setPrimaryButtonVisibility_buttonsLayoutShouldBeVisible() {
+        mCardPreference.setPrimaryButtonVisible(true);
+
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_setPrimaryButtonVisibility_shouldApplyToPrimaryButton() {
+        mCardPreference.setPrimaryButtonVisible(true);
+
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getPrimaryButton().getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_setSecondaryButtonVisibility_buttonsLayoutShouldBeVisible() {
+        mCardPreference.setSecondaryButtonVisible(true);
+
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_setSecondaryButtonVisibility_shouldApplyToSecondaryButton() {
+        mCardPreference.setSecondaryButtonVisible(true);
+
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getSecondaryButton().getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_setPrimaryButtonText_shouldApplyToPrimaryButton() {
+        String expectedText = "primary-button";
+        mCardPreference.setPrimaryButtonText(expectedText);
+
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getPrimaryButton().getText().toString()).isEqualTo(expectedText);
+    }
+
+    @Test
+    public void onBindViewHolder_setSecondaryButtonText_shouldApplyToSecondaryButton() {
+        String expectedText = "secondary-button";
+        mCardPreference.setSecondaryButtonText(expectedText);
+
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getSecondaryButton().getText().toString()).isEqualTo(expectedText);
+    }
+
+    @Test
+    public void onBindViewHolder_initialTextForPrimaryButtonShouldBeEmpty() {
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getPrimaryButton().getText().toString()).isEqualTo("");
+    }
+
+    @Test
+    public void onBindViewHolder_initialTextForSecondaryButtonShouldBeEmpty() {
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getSecondaryButton().getText().toString()).isEqualTo("");
+    }
+
+    @Test
+    public void performClickOnPrimaryButton_shouldCalledClickListener() {
+        final boolean[] hasCalled = {false};
+        View.OnClickListener clickListener = v -> hasCalled[0] = true;
+        mCardPreference.setPrimaryButtonClickListener(clickListener);
+
+        mCardPreference.onBindViewHolder(mHolder);
+        getPrimaryButton().performClick();
+
+        assertThat(hasCalled[0]).isTrue();
+    }
+
+    @Test
+    public void performClickOnSecondaryButton_shouldCalledClickListener() {
+        final boolean[] hasCalled = {false};
+        View.OnClickListener clickListener = v -> hasCalled[0] = true;
+        mCardPreference.setSecondaryButtonClickListener(clickListener);
+
+        mCardPreference.onBindViewHolder(mHolder);
+        getSecondaryButton().performClick();
+
+        assertThat(hasCalled[0]).isTrue();
+    }
+
+    @Test
+    public void onBindViewHolder_primaryButtonDefaultIsGone() {
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getPrimaryButton().getVisibility()).isEqualTo(GONE);
+    }
+
+    @Test
+    public void onBindViewHolder_secondaryButtonDefaultIsGone() {
+        mCardPreference.onBindViewHolder(mHolder);
+
+        assertThat(getSecondaryButton().getVisibility()).isEqualTo(GONE);
+    }
+
+    @Test
+    public void setPrimaryButtonVisibility_setTrueAfterBindViewHolder_shouldBeVisible() {
+        mCardPreference.setPrimaryButtonVisible(false);
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setPrimaryButtonVisible(true);
+
+        assertThat(getPrimaryButton().getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    public void setPrimaryButtonText_setAfterBindViewHolder_setOnUi() {
+        String expectedText = "123456";
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setPrimaryButtonText(expectedText);
+
+        assertThat(getPrimaryButton().getText().toString()).isEqualTo(expectedText);
+    }
+
+    @Test
+    public void setPrimaryButtonText_setNull_shouldBeEmptyText() {
+        final String emptyString = "";
+        mCardPreference.setPrimaryButtonText("1234");
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setPrimaryButtonText(null);
+
+        assertThat(getPrimaryButton().getText().toString()).isEqualTo(emptyString);
+    }
+
+    @Test
+    public void setPrimaryButtonClickListener_setAfterOnBindViewHolder() {
+        final String[] hasCalled = {""};
+        String expectedClickedResult = "was called";
+        View.OnClickListener clickListener = v -> hasCalled[0] = expectedClickedResult;
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setPrimaryButtonClickListener(clickListener);
+        getPrimaryButton().performClick();
+
+        assertThat(hasCalled[0]).isEqualTo(expectedClickedResult);
+    }
+
+    @Test
+    public void setPrimaryButtonClickListener_setNull_shouldClearTheOnClickListener() {
+        final String[] hasCalled = {"not called"};
+        View.OnClickListener clickListener = v -> hasCalled[0] = "called once";
+        mCardPreference.setPrimaryButtonClickListener(clickListener);
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setPrimaryButtonClickListener(null);
+        getPrimaryButton().performClick();
+
+        assertThat(hasCalled[0]).isEqualTo("not called");
+    }
+
+    @Test
+    public void setSecondaryButtonVisibility_setTrueAfterBindViewHolder_shouldBeVisible() {
+        mCardPreference.setSecondaryButtonVisible(false);
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setSecondaryButtonVisible(true);
+
+        assertThat(getSecondaryButton().getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    public void setSecondaryButtonText_setAfterBindViewHolder_setOnUi() {
+        String expectedText = "10101010";
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setSecondaryButtonText(expectedText);
+
+        assertThat(getSecondaryButton().getText().toString()).isEqualTo(expectedText);
+    }
+
+    @Test
+    public void setSecondaryButtonText_setNull_shouldBeEmptyText() {
+        String emptyString = "";
+        mCardPreference.setSecondaryButtonText("1234");
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setSecondaryButtonText(null);
+
+        assertThat(getSecondaryButton().getText().toString()).isEqualTo(emptyString);
+    }
+
+    @Test
+    public void setSecondaryButtonClickListener_setAfterOnBindViewHolder() {
+        final String[] hasCalled = {""};
+        String expectedClickedResult = "2nd was called";
+        View.OnClickListener clickListener = v -> hasCalled[0] = expectedClickedResult;
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setSecondaryButtonClickListener(clickListener);
+        getSecondaryButton().performClick();
+
+        assertThat(hasCalled[0]).isEqualTo(expectedClickedResult);
+    }
+
+    @Test
+    public void setSecondaryButtonClickListener_setNull_shouldClearTheOnClickListener() {
+        final String[] hasCalled = {"not called"};
+        View.OnClickListener clickListener = v -> hasCalled[0] = "called once";
+        mCardPreference.setSecondaryButtonClickListener(clickListener);
+        mCardPreference.onBindViewHolder(mHolder);
+
+        mCardPreference.setSecondaryButtonClickListener(null);
+        getSecondaryButton().performClick();
+
+        assertThat(hasCalled[0]).isEqualTo("not called");
+    }
+
+    @Test
+    public void
+            setPrimaryButtonVisibility_onlyPrimaryButtonVisible_setGone_buttonGroupShouldBeGone() {
+        mCardPreference.setPrimaryButtonVisible(true);
+        mCardPreference.setSecondaryButtonVisible(false);
+        mCardPreference.onBindViewHolder(mHolder);
+        assertWithMessage("PreCondition: buttonsView should be Visible")
+                .that(getCardPreferenceButtonsView().getVisibility())
+                .isEqualTo(VISIBLE);
+
+        mCardPreference.setPrimaryButtonVisible(false);
+
+        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
+    }
+
+    @Test
+    public void
+            setSecondaryButtonVisibility_only2ndButtonVisible_setGone_buttonGroupShouldBeGone() {
+        mCardPreference.setPrimaryButtonVisible(false);
+        mCardPreference.setSecondaryButtonVisible(true);
+        mCardPreference.onBindViewHolder(mHolder);
+        assertWithMessage("PreCondition: buttonsView should be Visible")
+                .that(getCardPreferenceButtonsView().getVisibility())
+                .isEqualTo(VISIBLE);
+
+        mCardPreference.setSecondaryButtonVisible(false);
+
+        assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
+    }
+
+    private View getCardPreferenceButtonsView() {
+        return mHolder.findViewById(R.id.card_preference_buttons);
+    }
+
+    private Button getPrimaryButton() {
+        return (Button) mHolder.findViewById(android.R.id.button1);
+    }
+
+    private Button getSecondaryButton() {
+        return (Button) mHolder.findViewById(android.R.id.button2);
     }
 }