Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 966072d6 authored by Jacqueline Bronger's avatar Jacqueline Bronger
Browse files

Audio output switcher: show low battery in color

Bug: 292067610
Test: atest CachedBluetoothDeviceTest

Change-Id: I820d4529a80e8e1dcfcb0ddc1348edb43a7bab51
parent b7c89a1b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -202,10 +202,18 @@
    <string name="bluetooth_active_battery_level_untethered">Active, L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery</string>
    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level. [CHAR LIMIT=NONE] -->
    <string name="bluetooth_battery_level"><xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery</string>
    <!-- Connected devices settings. Message on TV when Bluetooth is connected but not in use, showing remote device battery level. [CHAR LIMIT=NONE] -->
    <string name="tv_bluetooth_battery_level">Battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string>
    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for untethered headset. [CHAR LIMIT=NONE] -->
    <string name="bluetooth_battery_level_untethered">L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery</string>
    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the left part of the untethered headset. [CHAR LIMIT=NONE] -->
    <string name="bluetooth_battery_level_untethered_left">Left <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the right part of the untethered headset. [CHAR LIMIT=NONE] -->
    <string name="bluetooth_battery_level_untethered_right">Right <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
    <!-- Connected devices settings. Message when Bluetooth is connected and active but no battery information, showing remote device status. [CHAR LIMIT=NONE] -->
    <string name="bluetooth_active_no_battery_level">Active</string>
    <!-- Connected devices settings. Message shown when bluetooth device is disconnected but is a known, previously connected device [CHAR LIMIT=NONE] -->
    <string name="bluetooth_saved_device">Saved</string>

    <!-- Connected device settings. Message when the left-side hearing aid device is active. [CHAR LIMIT=NONE] -->
    <string name="bluetooth_hearing_aid_left_active">Active, left only</string>
+121 −7
Original line number Diff line number Diff line
@@ -34,7 +34,9 @@ import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
@@ -44,6 +46,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.settingslib.R;
import com.android.settingslib.Utils;
import com.android.settingslib.media.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;

@@ -73,6 +76,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    private static final long MAX_LEAUDIO_DELAY_FOR_AUTO_CONNECT = 30000;
    private static final long MAX_MEDIA_PROFILE_CONNECT_DELAY = 60000;

    private static final int DEFAULT_LOW_BATTERY_THRESHOLD = 20;

    // To be used instead of a resource id to indicate that low battery states should not be
    // changed to a different color.
    private static final int SUMMARY_NO_COLOR_FOR_LOW_BATTERY = 0;

    private final Context mContext;
    private final BluetoothAdapter mLocalAdapter;
    private final LocalBluetoothProfileManager mProfileManager;
@@ -1150,6 +1159,46 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
     * @param shortSummary {@code true} if need to return short version summary
     */
    public String getConnectionSummary(boolean shortSummary) {
        CharSequence summary = getConnectionSummary(shortSummary, false /* isTvSummary */,
                SUMMARY_NO_COLOR_FOR_LOW_BATTERY);
        if (summary != null) {
            return summary.toString();
        }
        return null;
    }

    /**
     * Returns android tv string that describes the connection state of this device.
     */
    public CharSequence getTvConnectionSummary() {
        return getTvConnectionSummary(SUMMARY_NO_COLOR_FOR_LOW_BATTERY);
    }

    /**
     * Returns android tv string that describes the connection state of this device, with low
     * battery states highlighted in color.
     *
     * @param lowBatteryColorRes - resource id for the color that should be used for the part of the
     *                           CharSequence that contains low battery information.
     */
    public CharSequence getTvConnectionSummary(int lowBatteryColorRes) {
        return getConnectionSummary(false /* shortSummary */, true /* isTvSummary */,
                lowBatteryColorRes);
    }

    /**
     * Return summary that describes connection state of this device. Summary depends on:
     * 1. Whether device has battery info
     * 2. Whether device is in active usage(or in phone call)
     *
     * @param shortSummary       {@code true} if need to return short version summary
     * @param isTvSummary        {@code true} if the summary should be TV specific
     * @param lowBatteryColorRes Resource id of the color to be used for low battery strings. Use
     *                           {@link SUMMARY_NO_COLOR_FOR_LOW_BATTERY} if no separate color
     *                           should be used.
     */
    private CharSequence getConnectionSummary(boolean shortSummary, boolean isTvSummary,
            int lowBatteryColorRes) {
        boolean profileConnected = false;    // Updated as long as BluetoothProfile is connected
        boolean a2dpConnected = true;        // A2DP is connected
        boolean hfpConnected = true;         // HFP is connected
@@ -1276,17 +1325,82 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
            }
        }

        if (stringRes != R.string.bluetooth_pairing
                || getBondState() == BluetoothDevice.BOND_BONDING) {
        if (stringRes == R.string.bluetooth_pairing
                && getBondState() != BluetoothDevice.BOND_BONDING) {
            return null;
        }

        boolean summaryIncludesBatteryLevel = stringRes == R.string.bluetooth_battery_level
                || stringRes == R.string.bluetooth_active_battery_level
                || stringRes == R.string.bluetooth_active_battery_level_untethered
                || stringRes == R.string.bluetooth_battery_level_untethered;
        if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) {
            return getTvBatterySummary(batteryLevel, leftBattery, rightBattery, lowBatteryColorRes);
        }

        if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
            return mContext.getString(stringRes, Utils.formatPercentage(leftBattery),
                    Utils.formatPercentage(rightBattery));
        } else {
            return mContext.getString(stringRes, batteryLevelPercentageString);
        }
    }

    private CharSequence getTvBatterySummary(int mainBattery, int leftBattery, int rightBattery,
            int lowBatteryColorRes) {
        // Since there doesn't seem to be a way to use format strings to add the
        // percentages and also mark which part of the string is left and right to color
        // them, we are using one string resource per battery.
        Resources res = mContext.getResources();
        SpannableStringBuilder spannableBuilder = new SpannableStringBuilder();
        if (leftBattery >= 0 || rightBattery >= 0) {
            // Not switching the left and right for RTL to keep the left earbud always on
            // the left.
            if (leftBattery >= 0) {
                String left = res.getString(
                        R.string.bluetooth_battery_level_untethered_left,
                        Utils.formatPercentage(leftBattery));
                addBatterySpan(spannableBuilder, left, isBatteryLow(leftBattery,
                                BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD),
                        lowBatteryColorRes);
            }
            if (rightBattery >= 0) {
                if (!spannableBuilder.isEmpty()) {
                    spannableBuilder.append(" ");
                }
                String right = res.getString(
                        R.string.bluetooth_battery_level_untethered_right,
                        Utils.formatPercentage(rightBattery));
                addBatterySpan(spannableBuilder, right, isBatteryLow(rightBattery,
                                BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD),
                        lowBatteryColorRes);
            }
        } else {
            return null;
            addBatterySpan(spannableBuilder, res.getString(R.string.tv_bluetooth_battery_level,
                            Utils.formatPercentage(mainBattery)),
                    isBatteryLow(mainBattery, BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD),
                    lowBatteryColorRes);
        }
        return spannableBuilder;
    }

    private void addBatterySpan(SpannableStringBuilder builder,
            String batteryString, boolean lowBattery, int lowBatteryColorRes) {
        if (lowBattery && lowBatteryColorRes != SUMMARY_NO_COLOR_FOR_LOW_BATTERY) {
            builder.append(batteryString,
                    new ForegroundColorSpan(mContext.getResources().getColor(lowBatteryColorRes)),
                    0 /* flags */);
        } else {
            builder.append(batteryString);
        }
    }

    private boolean isBatteryLow(int batteryLevel, int metadataKey) {
        int lowBatteryThreshold = BluetoothUtils.getIntMetaData(mDevice, metadataKey);
        if (lowBatteryThreshold <= 0) {
            lowBatteryThreshold = DEFAULT_LOW_BATTERY_THRESHOLD;
        }
        return batteryLevel <= lowBatteryThreshold;
    }

    private boolean isTwsBatteryAvailable(int leftBattery, int rightBattery) {
+7 −0
Original line number Diff line number Diff line
@@ -71,6 +71,13 @@ public class BluetoothMediaDevice extends MediaDevice {
                : mContext.getString(R.string.bluetooth_disconnected);
    }

    @Override
    public CharSequence getSummaryForTv(int lowBatteryColorRes) {
        return isConnected() || mCachedDevice.isBusy()
                ? mCachedDevice.getTvConnectionSummary(lowBatteryColorRes)
                : mContext.getString(R.string.bluetooth_saved_device);
    }

    @Override
    public int getSelectionBehavior() {
        // We don't allow apps to override the selection behavior of system routes.
+11 −0
Original line number Diff line number Diff line
@@ -207,6 +207,17 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
     */
    public abstract String getSummary();

    /**
     * Get summary from MediaDevice for TV with low batter states in a different color if
     * applicable.
     *
     * @param lowBatteryColorRes Color resource for the part of the CharSequence that describes a
     *                           low battery state.
     */
    public CharSequence getSummaryForTv(int lowBatteryColorRes) {
        return getSummary();
    }

    /**
     * Get icon of MediaDevice.
     *
+2 −0
Original line number Diff line number Diff line
@@ -49,6 +49,8 @@ android_robolectric_test {
        "androidx.fragment_fragment",
        "androidx.test.core",
        "androidx.core_core",
        "flag-junit",
        "settingslib_flags_lib",
        "testng", // TODO: remove once JUnit on Android provides assertThrows
    ],
    java_resource_dirs: ["config"],
Loading