diff --git a/res/drawable-night/ic_battery_status_protected_24dp.xml b/res/drawable-night/ic_battery_status_protected_24dp.xml new file mode 100644 index 00000000000..23386cb8048 --- /dev/null +++ b/res/drawable-night/ic_battery_status_protected_24dp.xml @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_battery_status_protected_24dp.xml b/res/drawable/ic_battery_status_protected_24dp.xml new file mode 100644 index 00000000000..8841710e43d --- /dev/null +++ b/res/drawable/ic_battery_status_protected_24dp.xml @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 6352c2b46e0..3d8f2f414b3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6416,8 +6416,18 @@ Charging is paused Protecting battery to extend battery lifespan - - %1$s + + Charging to %1$s to protect the battery + + When your tablet is docked, charging will be paused at %1$s to extend battery lifespan + + Charging paused to protect battery + + When your tablet is docked, charging is paused at %1$s to extend battery lifespan + + Charging to full + + To protect your battery, charging will be paused at %1$s the next time your tablet is docked Learn more about charging is paused diff --git a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java index 558e0bfb391..665be1f0f2c 100644 --- a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java @@ -98,6 +98,7 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + intentFilter.addAction(BatteryUtils.BYPASS_DOCK_DEFENDER_ACTION); final Intent intent = mContext.registerReceiver(this, intentFilter); updateBatteryStatus(intent, true /* forceUpdate */); @@ -132,6 +133,8 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver { mBatteryHealth = batteryHealth; } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) { mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_SAVER); + } else if (BatteryUtils.BYPASS_DOCK_DEFENDER_ACTION.equals(intent.getAction())) { + mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_STATUS); } } } diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index b1e4c34e6ea..52d6d58061c 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -42,6 +42,8 @@ import com.android.settingslib.fuelgauge.EstimateKt; import com.android.settingslib.utils.PowerUtil; import com.android.settingslib.utils.StringUtil; +import java.text.NumberFormat; + public class BatteryInfo { private static final String TAG = "BatteryInfo"; @@ -49,6 +51,7 @@ public class BatteryInfo { public CharSequence remainingLabel; public int batteryLevel; public int batteryStatus; + public int pluggedStatus; public boolean discharging = true; public boolean isOverheated; public long remainingTimeUs = 0; @@ -253,7 +256,8 @@ public class BatteryInfo { info.mBatteryUsageStats = batteryUsageStats; info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast); info.batteryPercentString = Utils.formatPercentage(info.batteryLevel); - info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; + info.pluggedStatus = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); + info.mCharging = info.pluggedStatus != 0; info.averageTimeToDischarge = estimate.getAverageDischargeTime(); info.isOverheated = batteryBroadcast.getIntExtra( BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN) @@ -280,25 +284,37 @@ public class BatteryInfo { BatteryManager.BATTERY_STATUS_UNKNOWN); info.discharging = false; info.suggestionLabel = null; - if (info.isOverheated && status != BatteryManager.BATTERY_STATUS_FULL) { + int dockDefenderMode = BatteryUtils.getCurrentDockDefenderMode(context, info); + if ((info.isOverheated && status != BatteryManager.BATTERY_STATUS_FULL + && dockDefenderMode == BatteryUtils.DockDefenderMode.DISABLED) + || dockDefenderMode == BatteryUtils.DockDefenderMode.ACTIVE) { + // Battery defender active, battery charging paused info.remainingLabel = null; int chargingLimitedResId = R.string.power_charging_limited; - info.chargeLabel = - context.getString(chargingLimitedResId, info.batteryPercentString); - } else if (chargeTimeMs > 0 && status != BatteryManager.BATTERY_STATUS_FULL) { + info.chargeLabel = context.getString(chargingLimitedResId, info.batteryPercentString); + } else if ((chargeTimeMs > 0 && status != BatteryManager.BATTERY_STATUS_FULL + && dockDefenderMode == BatteryUtils.DockDefenderMode.DISABLED) + || dockDefenderMode == BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED) { + // Battery is charging to full info.remainingTimeUs = PowerUtil.convertMsToUs(chargeTimeMs); - final CharSequence timeString = StringUtil.formatElapsedTime( - context, - PowerUtil.convertUsToMs(info.remainingTimeUs), - false /* withSeconds */, + final CharSequence timeString = StringUtil.formatElapsedTime(context, + (double) PowerUtil.convertUsToMs(info.remainingTimeUs), false /* withSeconds */, true /* collapseTimeUnit */); int resId = R.string.power_charging_duration; - info.remainingLabel = context.getString( - R.string.power_remaining_charging_duration_only, timeString); + info.remainingLabel = context.getString(R.string.power_remaining_charging_duration_only, + timeString); info.chargeLabel = context.getString(resId, info.batteryPercentString, timeString); + } else if (dockDefenderMode == BatteryUtils.DockDefenderMode.FUTURE_BYPASS) { + // Dock defender will be triggered in the future, charging will be paused at 90%. + final int extraValue = context.getResources().getInteger( + R.integer.config_battery_extra_tip_value); + final String extraPercentage = NumberFormat.getPercentInstance().format( + extraValue * 0.01f); + info.chargeLabel = context.getString(R.string.power_charging_future_paused, + info.batteryPercentString, extraPercentage); } else { - final String chargeStatusLabel = - Utils.getBatteryStatus(context, batteryBroadcast, compactStatus); + final String chargeStatusLabel = Utils.getBatteryStatus(context, batteryBroadcast, + compactStatus); info.remainingLabel = null; info.chargeLabel = info.batteryLevel == 100 ? info.batteryPercentString : resources.getString(R.string.power_charging, info.batteryPercentString, diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index a6c48a48d6b..e9b72d0f51a 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -24,6 +24,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.BatteryConsumer; +import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; @@ -33,6 +34,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.UidBatteryConsumer; import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import androidx.annotation.IntDef; @@ -72,6 +74,11 @@ public class BatteryUtils { /** Special UID for aggregated other users. */ public static final long UID_OTHER_USERS = Long.MIN_VALUE; + /** Flag to check if the dock defender mode has been temporarily bypassed */ + public static final String SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS = "dock_defender_bypass"; + + public static final String BYPASS_DOCK_DEFENDER_ACTION = "battery.dock.defender.bypass"; + @Retention(RetentionPolicy.SOURCE) @IntDef({StatusType.SCREEN_USAGE, StatusType.FOREGROUND, @@ -85,6 +92,18 @@ public class BatteryUtils { int ALL = 3; } + @Retention(RetentionPolicy.SOURCE) + @IntDef({DockDefenderMode.FUTURE_BYPASS, + DockDefenderMode.ACTIVE, + DockDefenderMode.TEMPORARILY_BYPASSED, + DockDefenderMode.DISABLED}) + public @interface DockDefenderMode { + int FUTURE_BYPASS = 0; + int ACTIVE = 1; + int TEMPORARILY_BYPASSED = 2; + int DISABLED = 3; + } + private static final String TAG = "BatteryUtils"; private static BatteryUtils sInstance; @@ -570,4 +589,21 @@ public class BatteryUtils { return -1L; } + + /** Gets the current dock defender mode */ + public static int getCurrentDockDefenderMode(Context context, BatteryInfo batteryInfo) { + if (batteryInfo.pluggedStatus == BatteryManager.BATTERY_PLUGGED_DOCK) { + if (Settings.Global.getInt(context.getContentResolver(), + SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0) == 1) { + return DockDefenderMode.TEMPORARILY_BYPASSED; + } else if (batteryInfo.isOverheated && FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context) + .isExtraDefend()) { + return DockDefenderMode.ACTIVE; + } else if (!batteryInfo.isOverheated) { + return DockDefenderMode.FUTURE_BYPASS; + } + } + return DockDefenderMode.DISABLED; + } } diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java index 94a93b83be0..9b6f50f17dc 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java @@ -147,7 +147,7 @@ public interface PowerUsageFeatureProvider { /** * Gets a intent for one time bypass charge limited to resume charging. */ - Intent getResumeChargeIntent(); + Intent getResumeChargeIntent(boolean isDockDefender); /** * Returns battery history data with corresponding timestamp key. diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java index 0adfc9d1537..cc802e8d30a 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java @@ -156,7 +156,7 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider } @Override - public Intent getResumeChargeIntent() { + public Intent getResumeChargeIntent(boolean isDockDefender) { return null; } diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java index 95145ba8216..7bdc5d5686a 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java @@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting; 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.DockDefenderDetector; import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector; import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector; import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector; @@ -74,6 +75,7 @@ public class BatteryTipLoader extends AsyncLoaderCompat> { tips.add(new EarlyWarningDetector(policy, context).detect()); tips.add(new BatteryDefenderDetector( batteryInfo, context.getApplicationContext()).detect()); + tips.add(new DockDefenderDetector(batteryInfo, context.getApplicationContext()).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 87d4a0b44a5..08df2e494f6 100644 --- a/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java @@ -37,11 +37,10 @@ public class BatteryDefenderDetector implements BatteryTipDetector { @Override public BatteryTip detect() { - if (mBatteryInfo.isOverheated) { - final boolean extraDefend = FeatureFactory.getFactory(mContext) - .getPowerUsageFeatureProvider(mContext) - .isExtraDefend(); - return new BatteryDefenderTip(BatteryTip.StateType.NEW, extraDefend); + if (mBatteryInfo.isOverheated && !FeatureFactory.getFactory(mContext) + .getPowerUsageFeatureProvider(mContext) + .isExtraDefend()) { + return new BatteryDefenderTip(BatteryTip.StateType.NEW); } return new BatteryDefenderTip(BatteryTip.StateType.INVISIBLE); } diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetector.java new file mode 100644 index 00000000000..8a839d392d8 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetector.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 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 android.content.Context; + +import com.android.settings.fuelgauge.BatteryInfo; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.DockDefenderTip; + +/** + * Detect whether the dock defender mode is enabled. + */ +public class DockDefenderDetector implements BatteryTipDetector { + private final BatteryInfo mBatteryInfo; + private final Context mContext; + + public DockDefenderDetector(BatteryInfo batteryInfo, Context context) { + mBatteryInfo = batteryInfo; + mContext = context; + } + + @Override + public BatteryTip detect() { + int mode = BatteryUtils.getCurrentDockDefenderMode(mContext, mBatteryInfo); + return new DockDefenderTip( + mode != BatteryUtils.DockDefenderMode.DISABLED + ? BatteryTip.StateType.NEW + : BatteryTip.StateType.INVISIBLE, + mode); + } + +} diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java index 2fb56500834..1ccc29c18e3 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java @@ -32,24 +32,15 @@ import com.android.settings.widget.CardPreference; import com.android.settingslib.HelpUtils; 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; - mShowDialog = false; + super(TipType.BATTERY_DEFENDER, state, false /* showDialog */); } private BatteryDefenderTip(Parcel in) { @@ -63,14 +54,6 @@ public class BatteryDefenderTip extends BatteryTip { @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); } @@ -131,7 +114,7 @@ public class BatteryDefenderTip extends BatteryTip { final Intent intent = FeatureFactory.getFactory(context) .getPowerUsageFeatureProvider(context) - .getResumeChargeIntent(); + .getResumeChargeIntent(false); if (intent != null) { context.sendBroadcast(intent); } diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java index 5aee0291e4d..fcf5e09ba1b 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java @@ -58,7 +58,8 @@ public abstract class BatteryTip implements Comparable, Parcelable { TipType.REDUCED_BATTERY, TipType.LOW_BATTERY, TipType.REMOVE_APP_RESTRICTION, - TipType.BATTERY_DEFENDER}) + TipType.BATTERY_DEFENDER, + TipType.DOCK_DEFENDER}) public @interface TipType { int SMART_BATTERY_MANAGER = 0; int APP_RESTRICTION = 1; @@ -69,6 +70,7 @@ public abstract class BatteryTip implements Comparable, Parcelable { int SUMMARY = 6; int REMOVE_APP_RESTRICTION = 7; int BATTERY_DEFENDER = 8; + int DOCK_DEFENDER = 9; } @VisibleForTesting @@ -78,12 +80,13 @@ public abstract class BatteryTip implements Comparable, Parcelable { TIP_ORDER.append(TipType.BATTERY_SAVER, 0); TIP_ORDER.append(TipType.LOW_BATTERY, 1); TIP_ORDER.append(TipType.BATTERY_DEFENDER, 2); - TIP_ORDER.append(TipType.APP_RESTRICTION, 3); - TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 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); + TIP_ORDER.append(TipType.DOCK_DEFENDER, 3); + TIP_ORDER.append(TipType.APP_RESTRICTION, 4); + TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 5); + TIP_ORDER.append(TipType.SUMMARY, 6); + TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 7); + TIP_ORDER.append(TipType.REDUCED_BATTERY, 8); + TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 9); } private static final String KEY_PREFIX = "key_battery_tip"; diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTip.java new file mode 100644 index 00000000000..2ba3dcd12e2 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTip.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2022 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.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.os.Parcel; +import android.util.Log; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.BatteryUtils.DockDefenderMode; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.widget.CardPreference; +import com.android.settingslib.HelpUtils; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import java.text.NumberFormat; + +/** + * Tip to show dock defender status + */ +public class DockDefenderTip extends BatteryTip { + private static final String TAG = "DockDefenderTip"; + private int mMode; + + public DockDefenderTip(@StateType int state, @DockDefenderMode int mode) { + super(TipType.DOCK_DEFENDER, state, false); + mMode = mode; + } + + private DockDefenderTip(Parcel in) { + super(in); + } + + public int getMode() { + return mMode; + } + + @Override + public CharSequence getTitle(Context context) { + switch (mMode) { + case DockDefenderMode.FUTURE_BYPASS: + return context.getString(R.string.battery_tip_dock_defender_future_bypass_title, + getExtraPercentage(context)); + case DockDefenderMode.ACTIVE: + return context.getString(R.string.battery_tip_dock_defender_active_title); + case DockDefenderMode.TEMPORARILY_BYPASSED: + return context.getString( + R.string.battery_tip_dock_defender_temporarily_bypassed_title); + default: + return null; + } + } + + @Override + public CharSequence getSummary(Context context) { + switch (mMode) { + case DockDefenderMode.FUTURE_BYPASS: + return context.getString(R.string.battery_tip_dock_defender_future_bypass_summary, + getExtraPercentage(context)); + case DockDefenderMode.ACTIVE: + return context.getString(R.string.battery_tip_dock_defender_active_summary, + getExtraPercentage(context)); + case DockDefenderMode.TEMPORARILY_BYPASSED: + return context.getString( + R.string.battery_tip_dock_defender_temporarily_bypassed_summary, + getExtraPercentage(context)); + default: + return null; + } + } + + @Override + public int getIconId() { + return R.drawable.ic_battery_status_protected_24dp; + } + + @Override + public void updateState(BatteryTip tip) { + mState = tip.mState; + if (tip instanceof DockDefenderTip) { + mMode = ((DockDefenderTip) tip).mMode; + } + } + + @Override + public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) { + metricsFeatureProvider.action(context, SettingsEnums.ACTION_DOCK_DEFENDER_TIP, + 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.setSelectable(false); + switch (mMode) { + case DockDefenderMode.FUTURE_BYPASS: + case DockDefenderMode.ACTIVE: + cardPreference.setPrimaryButtonText( + context.getString(R.string.battery_tip_charge_to_full_button)); + cardPreference.setPrimaryButtonClickListener(unused -> { + resumeCharging(context); + mMode = DockDefenderMode.TEMPORARILY_BYPASSED; + context.sendBroadcast(new Intent().setAction( + BatteryUtils.BYPASS_DOCK_DEFENDER_ACTION).setPackage( + context.getPackageName()).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND)); + updatePreference(preference); + }); + cardPreference.setPrimaryButtonVisible(true); + break; + case DockDefenderMode.TEMPORARILY_BYPASSED: + cardPreference.setPrimaryButtonVisible(false); + break; + default: + cardPreference.setVisible(false); + return; + } + + cardPreference.setSecondaryButtonText(context.getString(R.string.learn_more)); + //TODO: update helper string + cardPreference.setSecondaryButtonClickListener( + button -> button.startActivityForResult( + HelpUtils.getHelpIntent( + context, + context.getString(R.string.help_url_battery_defender), + /* backupContext */ ""), /* requestCode */ 0)); + cardPreference.setSecondaryButtonVisible(true); + cardPreference.setSecondaryButtonContentDescription(context.getString( + R.string.battery_tip_limited_temporarily_sec_button_content_description)); + + } + + 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(true); + if (intent != null) { + context.sendBroadcast(intent); + } + + Log.i(TAG, "send resume charging broadcast intent=" + intent); + } + + private String getExtraPercentage(Context context) { + final int extraValue = context.getResources() + .getInteger(R.integer.config_battery_extra_tip_value); + return NumberFormat.getPercentInstance() + .format(extraValue * 0.01f); + } + + public static final Creator CREATOR = new Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new DockDefenderTip(in); + } + + public BatteryTip[] newArray(int size) { + return new DockDefenderTip[size]; + } + }; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java index 867d8f44b7e..79bd84bd1ec 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java @@ -55,6 +55,7 @@ public class BatteryBroadcastReceiverTest { private BatteryBroadcastReceiver mBatteryBroadcastReceiver; private Context mContext; private Intent mChargingIntent; + private Intent mDockDefenderBypassIntent; @Before public void setUp() { @@ -72,6 +73,8 @@ public class BatteryBroadcastReceiverTest { mChargingIntent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_INTENT_SCALE); mChargingIntent .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING); + mDockDefenderBypassIntent = new Intent(BatteryUtils.BYPASS_DOCK_DEFENDER_ACTION); + } @Test @@ -130,6 +133,13 @@ public class BatteryBroadcastReceiverTest { verify(mBatteryListener, never()).onBatteryChanged(anyInt()); } + @Test + public void testOnReceive_dockDefenderBypassed_listenerInvoked() { + mBatteryBroadcastReceiver.onReceive(mContext, mDockDefenderBypassIntent); + + verify(mBatteryListener).onBatteryChanged(BatteryUpdateType.BATTERY_STATUS); + } + @Test public void testRegister_updateBatteryStatus() { doReturn(mChargingIntent).when(mContext).registerReceiver(any(), any()); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java index c5c47d27203..209dc898f31 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java @@ -36,6 +36,7 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.SystemClock; +import android.provider.Settings; import android.util.SparseIntArray; import com.android.internal.os.BatteryStatsHistoryIterator; @@ -66,6 +67,7 @@ public class BatteryInfoTest { private static final String STATUS_CHARGING_NO_TIME = "50% - charging"; private static final String STATUS_CHARGING_TIME = "50% - 0 min left until full"; private static final String STATUS_NOT_CHARGING = "Not charging"; + private static final String STATUS_CHARGING_FUTURE_BYPASS = "50% - Charging to 12%"; private static final long REMAINING_TIME_NULL = -1; private static final long REMAINING_TIME = 2; // Strings are defined in frameworks/base/packages/SettingsLib/res/values/strings.xml @@ -97,6 +99,10 @@ public class BatteryInfoTest { mDisChargingBatteryBroadcast = BatteryTestUtils.getDischargingIntent(); mChargingBatteryBroadcast = BatteryTestUtils.getChargingIntent(); + + doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); + Settings.Global.putInt(mContext.getContentResolver(), + BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0); } @Test @@ -231,6 +237,7 @@ public class BatteryInfoTest { BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast, mBatteryUsageStats, MOCK_ESTIMATE, SystemClock.elapsedRealtime() * 1000, false /* shortString */); + assertThat(info.remainingTimeUs).isEqualTo(TEST_CHARGE_TIME_REMAINING); assertThat(info.remainingLabel.toString()) .isEqualTo(TEST_CHARGE_TIME_REMAINING_STRINGIFIED); @@ -265,6 +272,62 @@ public class BatteryInfoTest { assertThat(info.chargeLabel.toString()).contains(expectedString); } + @Test + public void testGetBatteryInfo_dockDefenderActive_updateChargeString() { + final String expectedString = + mContext.getString(R.string.battery_tip_limited_temporarily_title); + doReturn(TEST_CHARGE_TIME_REMAINING / 1000) + .when(mBatteryUsageStats).getChargeTimeRemainingMs(); + doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); + Intent intent = BatteryTestUtils.getCustomBatteryIntent(BatteryManager.BATTERY_PLUGGED_DOCK, + 50 /* level */, + 100 /* scale */, + BatteryManager.BATTERY_STATUS_CHARGING) + .putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT); + + BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, intent, + mBatteryUsageStats, MOCK_ESTIMATE, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); + + assertThat(info.chargeLabel.toString()).contains(expectedString); + } + + @Test + public void testGetBatteryInfo_dockDefenderTemporarilyBypassed_updateChargeLabel() { + doReturn(REMAINING_TIME).when(mBatteryUsageStats).getChargeTimeRemainingMs(); + mChargingBatteryBroadcast + .putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD); + Settings.Global.putInt(mContext.getContentResolver(), + BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 1); + + BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, + BatteryTestUtils.getCustomBatteryIntent(BatteryManager.BATTERY_PLUGGED_DOCK, + 50 /* level */, + 100 /* scale */, + BatteryManager.BATTERY_STATUS_CHARGING), + mBatteryUsageStats, MOCK_ESTIMATE, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); + + assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_TIME); + } + + @Test + public void testGetBatteryInfo_dockDefenderFutureBypass_updateChargeLabel() { + doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); + mChargingBatteryBroadcast + .putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD); + + BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, + BatteryTestUtils.getCustomBatteryIntent(BatteryManager.BATTERY_PLUGGED_DOCK, + 50 /* level */, + 100 /* scale */, + BatteryManager.BATTERY_STATUS_CHARGING), + mBatteryUsageStats, MOCK_ESTIMATE, SystemClock.elapsedRealtime() * 1000, + false /* shortString */); + + assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_FUTURE_BYPASS); + } + // Make our battery stats return a sequence of battery events. private void mockBatteryStatsHistory() { // Mock out new data every time iterateBatteryStatsHistory is called. diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java index 66a5e7f8bd0..648685a8d8f 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java @@ -132,7 +132,12 @@ public class PowerUsageFeatureProviderImplTest { } @Test - public void testGetResumeChargeIntent_returnNull() { - assertThat(mPowerFeatureProvider.getResumeChargeIntent()).isNull(); + public void testGetResumeChargeIntentWithoutDockDefender_returnNull() { + assertThat(mPowerFeatureProvider.getResumeChargeIntent(false)).isNull(); + } + + @Test + public void testGetResumeChargeIntentWithDockDefender_returnNull() { + assertThat(mPowerFeatureProvider.getResumeChargeIntent(true)).isNull(); } } 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 95280b669bc..6d3965e2266 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java @@ -53,6 +53,7 @@ public class BatteryTipLoaderTest { BatteryTip.TipType.BATTERY_SAVER, BatteryTip.TipType.LOW_BATTERY, BatteryTip.TipType.BATTERY_DEFENDER, + BatteryTip.TipType.DOCK_DEFENDER, BatteryTip.TipType.HIGH_DEVICE_USAGE, BatteryTip.TipType.SMART_BATTERY_MANAGER}; @Mock(answer = Answers.RETURNS_DEEP_STUBS) 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 90e7ad762a8..f81a4be8d63 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 @@ -18,10 +18,15 @@ package com.android.settings.fuelgauge.batterytip.detectors; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import android.content.Context; + import androidx.test.core.app.ApplicationProvider; import com.android.settings.fuelgauge.BatteryInfo; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; import org.junit.Test; @@ -36,6 +41,9 @@ public class BatteryDefenderDetectorTest { @Mock private BatteryInfo mBatteryInfo; private BatteryDefenderDetector mBatteryDefenderDetector; + private Context mContext; + + private FakeFeatureFactory mFakeFeatureFactory; @Before public void setUp() { @@ -43,20 +51,42 @@ public class BatteryDefenderDetectorTest { mBatteryInfo.discharging = false; + mContext = ApplicationProvider.getApplicationContext(); + mBatteryDefenderDetector = new BatteryDefenderDetector( - mBatteryInfo, ApplicationProvider.getApplicationContext()); + mBatteryInfo, mContext); + + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); } @Test - public void testDetect_notOverheated_tipInvisible() { + public void testDetect_notOverheatedNotExtraDefend_tipInvisible() { mBatteryInfo.isOverheated = false; + when(mFakeFeatureFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(false); assertThat(mBatteryDefenderDetector.detect().isVisible()).isFalse(); } @Test - public void testDetect_isOverheated_tipNew() { + public void testDetect_notOverheatedIsExtraDefend_tipInvisible() { + mBatteryInfo.isOverheated = false; + when(mFakeFeatureFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(true); + + assertThat(mBatteryDefenderDetector.detect().isVisible()).isFalse(); + } + + @Test + public void testDetect_isOverheatedIsExtraDefend_tipInvisible() { + mBatteryInfo.isOverheated = false; + when(mFakeFeatureFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(true); + + assertThat(mBatteryDefenderDetector.detect().isVisible()).isFalse(); + } + + @Test + public void testDetect_isOverheatedNotExtraDefend_tipNew() { mBatteryInfo.isOverheated = true; + when(mFakeFeatureFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(false); assertThat(mBatteryDefenderDetector.detect().getState()) .isEqualTo(BatteryTip.StateType.NEW); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetectorTest.java new file mode 100644 index 00000000000..9652a00ca18 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetectorTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.fuelgauge.BatteryInfo; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.DockDefenderTip; +import com.android.settings.testutils.BatteryTestUtils; +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class DockDefenderDetectorTest { + + private BatteryInfo mBatteryInfo; + private DockDefenderDetector mDockDefenderDetector; + private Context mContext; + private FakeFeatureFactory mFakeFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + mBatteryInfo = new BatteryInfo(); + mBatteryInfo.pluggedStatus = BatteryManager.BATTERY_PLUGGED_DOCK; + mDockDefenderDetector = new DockDefenderDetector(mBatteryInfo, mContext); + Intent intent = BatteryTestUtils.getCustomBatteryIntent(BatteryManager.BATTERY_PLUGGED_DOCK, + 50 /* level */, 100 /* scale */, BatteryManager.BATTERY_STATUS_CHARGING); + doReturn(intent).when(mContext).registerReceiver(eq(null), + refEq(new IntentFilter(Intent.ACTION_BATTERY_CHANGED))); + + Settings.Global.putInt(mContext.getContentResolver(), + BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0); + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); + } + + @Test + public void testDetect_dockDefenderTemporarilyBypassed() { + Settings.Global.putInt(mContext.getContentResolver(), + BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 1); + + BatteryTip batteryTip = mDockDefenderDetector.detect(); + + assertTrue(batteryTip instanceof DockDefenderTip); + assertEquals(((DockDefenderTip) batteryTip).getMode(), + BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED); + } + + @Test + public void testDetect_dockDefenderActive() { + mBatteryInfo.isOverheated = true; + doReturn(true).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); + + BatteryTip batteryTip = mDockDefenderDetector.detect(); + + assertTrue(batteryTip instanceof DockDefenderTip); + assertEquals(((DockDefenderTip) batteryTip).getMode(), + BatteryUtils.DockDefenderMode.ACTIVE); + } + + @Test + public void testDetect_dockDefenderFutureBypass() { + mBatteryInfo.isOverheated = false; + doReturn(false).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); + + BatteryTip batteryTip = mDockDefenderDetector.detect(); + + assertTrue(batteryTip instanceof DockDefenderTip); + assertEquals(((DockDefenderTip) batteryTip).getMode(), + BatteryUtils.DockDefenderMode.FUTURE_BYPASS); + } + + @Test + public void testDetect_overheatedTrue_dockDefenderDisabled() { + mBatteryInfo.isOverheated = true; + doReturn(false).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); + + BatteryTip batteryTip = mDockDefenderDetector.detect(); + + assertTrue(batteryTip instanceof DockDefenderTip); + assertEquals(((DockDefenderTip) batteryTip).getMode(), + BatteryUtils.DockDefenderMode.DISABLED); + } + + @Test + public void testDetect_pluggedInAC_dockDefenderDisabled() { + mBatteryInfo.pluggedStatus = BatteryManager.BATTERY_PLUGGED_AC; + + BatteryTip batteryTip = mDockDefenderDetector.detect(); + + assertTrue(batteryTip instanceof DockDefenderTip); + assertEquals(((DockDefenderTip) batteryTip).getMode(), + BatteryUtils.DockDefenderMode.DISABLED); + } + + @Test + public void testDetect_overheatedTrueAndDockDefenderNotTriggered_dockDefenderDisabled() { + doReturn(false).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); + mBatteryInfo.isOverheated = true; + + BatteryTip batteryTip = mDockDefenderDetector.detect(); + + assertTrue(batteryTip instanceof DockDefenderTip); + assertEquals(((DockDefenderTip) batteryTip).getMode(), + BatteryUtils.DockDefenderMode.DISABLED); + } +} 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 6bd6b26b78c..8b6033a8811 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 @@ -79,19 +79,11 @@ public class BatteryDefenderTipTest { } @Test - public void getSummary_notExtraDefended_showNonExtraDefendedSummary() { + public void getSummary_showSummary() { 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()) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTipTest.java new file mode 100644 index 00000000000..d917d8921a7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTipTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2022 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.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.util.Log; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.widget.CardPreference; +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.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLog; + +import java.text.NumberFormat; + +@RunWith(RobolectricTestRunner.class) +public class DockDefenderTipTest { + private Context mContext; + private DockDefenderTip mDockDefenderTipFutureBypass; + private DockDefenderTip mDockDefenderTipActive; + private DockDefenderTip mDockDefenderTipTemporarilyBypassed; + private DockDefenderTip mDockDefenderTipDisabled; + private FakeFeatureFactory mFeatureFactory; + private MetricsFeatureProvider mMetricsFeatureProvider; + + @Mock + private Preference mPreference; + @Mock + private CardPreference mCardPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = ApplicationProvider.getApplicationContext(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider; + + mDockDefenderTipFutureBypass = new DockDefenderTip(BatteryTip.StateType.NEW, + BatteryUtils.DockDefenderMode.FUTURE_BYPASS); + mDockDefenderTipActive = new DockDefenderTip(BatteryTip.StateType.NEW, + BatteryUtils.DockDefenderMode.ACTIVE); + mDockDefenderTipTemporarilyBypassed = new DockDefenderTip(BatteryTip.StateType.NEW, + BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED); + mDockDefenderTipDisabled = new DockDefenderTip(BatteryTip.StateType.INVISIBLE, + BatteryUtils.DockDefenderMode.DISABLED); + + doReturn(mContext).when(mPreference).getContext(); + doReturn(mContext).when(mCardPreference).getContext(); + } + + @Test + public void testGetTitle() { + assertThat(mDockDefenderTipFutureBypass.getTitle(mContext).toString()).isEqualTo( + mContext.getString(R.string.battery_tip_dock_defender_future_bypass_title, + getExtraPercentage(mContext))); + assertThat(mDockDefenderTipActive.getTitle(mContext).toString()).isEqualTo( + mContext.getString(R.string.battery_tip_dock_defender_active_title)); + assertThat(mDockDefenderTipTemporarilyBypassed.getTitle(mContext).toString()).isEqualTo( + mContext.getString(R.string.battery_tip_dock_defender_temporarily_bypassed_title)); + assertThat(mDockDefenderTipDisabled.getTitle(mContext)).isNull(); + } + + @Test + public void testGetSummary() { + assertThat(mDockDefenderTipFutureBypass.getSummary(mContext).toString()).isEqualTo( + mContext.getString(R.string.battery_tip_dock_defender_future_bypass_summary, + getExtraPercentage(mContext))); + assertThat(mDockDefenderTipActive.getSummary(mContext).toString()).isEqualTo( + mContext.getString(R.string.battery_tip_dock_defender_active_summary, + getExtraPercentage(mContext))); + assertThat(mDockDefenderTipTemporarilyBypassed.getSummary(mContext).toString()).isEqualTo( + mContext.getString(R.string.battery_tip_dock_defender_temporarily_bypassed_summary, + getExtraPercentage(mContext))); + assertThat(mDockDefenderTipDisabled.getSummary(mContext)).isNull(); + } + + @Test + public void testGetIconId() { + assertThat(mDockDefenderTipFutureBypass.getIconId()).isEqualTo( + R.drawable.ic_battery_status_protected_24dp); + } + + @Test + public void testUpdateState() { + mDockDefenderTipTemporarilyBypassed.updateState(mDockDefenderTipDisabled); + + assertThat(mDockDefenderTipTemporarilyBypassed.getState()).isEqualTo( + BatteryTip.StateType.INVISIBLE); + assertThat(mDockDefenderTipTemporarilyBypassed.getMode()).isEqualTo( + BatteryUtils.DockDefenderMode.DISABLED); + } + + @Test + public void testLog() { + mDockDefenderTipActive.log(mContext, mMetricsFeatureProvider); + + verify(mMetricsFeatureProvider).action(mContext, SettingsEnums.ACTION_DOCK_DEFENDER_TIP, + mDockDefenderTipActive.getState()); + } + + + @Test + public void testUpdatePreference_dockDefenderTipFutureBypass() { + mDockDefenderTipFutureBypass.updatePreference(mCardPreference); + + verify(mCardPreference).setPrimaryButtonVisible(true); + verify(mCardPreference).setPrimaryButtonText( + mContext.getString(R.string.battery_tip_charge_to_full_button)); + verifySecondaryButton(); + } + + @Test + public void testUpdatePreference_dockDefenderTipActive() { + mDockDefenderTipActive.updatePreference(mCardPreference); + + verify(mCardPreference).setPrimaryButtonVisible(true); + verify(mCardPreference).setPrimaryButtonText( + mContext.getString(R.string.battery_tip_charge_to_full_button)); + verifySecondaryButton(); + } + + @Test + public void testUpdatePreference_dockDefenderTipTemporarilyBypassed() { + mDockDefenderTipTemporarilyBypassed.updatePreference(mCardPreference); + + verify(mCardPreference).setPrimaryButtonVisible(false); + verify(mCardPreference, never()).setPrimaryButtonText(any()); + verifySecondaryButton(); + } + + private void verifySecondaryButton() { + verify(mCardPreference).setSecondaryButtonText(mContext.getString(R.string.learn_more)); + verify(mCardPreference).setSecondaryButtonVisible(true); + verify(mCardPreference).setSecondaryButtonContentDescription(mContext.getString( + R.string.battery_tip_limited_temporarily_sec_button_content_description)); + } + + @Test + public void updatePreference_castFail_logErrorMessage() { + mDockDefenderTipActive.updatePreference(mPreference); + + assertThat(getLastErrorLog()).isEqualTo("cast Preference to CardPreference failed"); + } + + private String getLastErrorLog() { + return ShadowLog.getLogsForTag(DockDefenderTip.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); + } + + private String getExtraPercentage(Context context) { + final int extraValue = context.getResources().getInteger( + R.integer.config_battery_extra_tip_value); + return NumberFormat.getPercentInstance().format(extraValue * 0.01f); + } + +} diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java index e4e26d2c531..a6f24309f52 100644 --- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java @@ -37,7 +37,7 @@ public class BatteryTestUtils { BatteryManager.BATTERY_STATUS_DISCHARGING); } - private static Intent getCustomBatteryIntent(int plugged, int level, int scale, int status) { + public static Intent getCustomBatteryIntent(int plugged, int level, int scale, int status) { Intent intent = new Intent(); intent.putExtra(BatteryManager.EXTRA_PLUGGED, plugged); intent.putExtra(BatteryManager.EXTRA_LEVEL, level);