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

Commit 28d9db3c authored by tim peng's avatar tim peng Committed by Android (Google) Code Review
Browse files

Merge "Show advanced Bluetooth information in device detail page" into sc-dev

parents 9138b055 90df1c9d
Loading
Loading
Loading
Loading
+75 −43
Original line number Diff line number Diff line
@@ -29,9 +29,10 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -43,7 +44,6 @@ import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.fuelgauge.BatteryMeterView;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -84,6 +84,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
    private static final int LEFT_DEVICE_ID = 1;
    private static final int RIGHT_DEVICE_ID = 2;
    private static final int CASE_DEVICE_ID = 3;
    private static final int MAIN_DEVICE_ID = 4;

    @VisibleForTesting
    LayoutPreference mLayoutPreference;
@@ -115,13 +116,11 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont

    @Override
    public int getAvailabilityStatus() {
        final boolean advancedEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
                SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, true);
        final boolean untetheredHeadset = mCachedDevice != null
                && BluetoothUtils.getBooleanMetaData(
                mCachedDevice.getDevice(), BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
        Log.d(TAG, "getAvailabilityStatus() is untethered : " + untetheredHeadset);
        return advancedEnabled && untetheredHeadset ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
        if (mCachedDevice == null) {
            return CONDITIONALLY_UNAVAILABLE;
        }
        return Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice())
                ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
    }

    @Override
@@ -182,7 +181,24 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
                updateDisconnectLayout();
                return;
            }
            final BluetoothDevice device = mCachedDevice.getDevice();
            final String deviceType = BluetoothUtils.getStringMetaData(device,
                    BluetoothDevice.METADATA_DEVICE_TYPE);
            if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
                    || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)) {
                mLayoutPreference.findViewById(R.id.layout_left).setVisibility(View.GONE);
                mLayoutPreference.findViewById(R.id.layout_right).setVisibility(View.GONE);

                updateSubLayout(mLayoutPreference.findViewById(R.id.layout_middle),
                        BluetoothDevice.METADATA_MAIN_ICON,
                        BluetoothDevice.METADATA_MAIN_BATTERY,
                        BluetoothDevice.METADATA_MAIN_CHARGING,
                        /* titleResId */ 0,
                        MAIN_DEVICE_ID);
            } else if (TextUtils.equals(deviceType,
                    BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
                    || BluetoothUtils.getBooleanMetaData(device,
                    BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
                updateSubLayout(mLayoutPreference.findViewById(R.id.layout_left),
                        BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
                        BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
@@ -205,6 +221,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
                        RIGHT_DEVICE_ID);
            }
        }
    }

    @VisibleForTesting
    Drawable createBtBatteryIcon(Context context, int level, boolean charging) {
@@ -226,17 +243,21 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
    }

    private void updateSubLayout(LinearLayout linearLayout, int iconMetaKey, int batteryMetaKey,
            int chargeMetaKey, int titleResId, int batteryId) {
            int chargeMetaKey, int titleResId, int deviceId) {
        if (linearLayout == null) {
            return;
        }
        final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice();
        final String iconUri = BluetoothUtils.getStringMetaData(bluetoothDevice, iconMetaKey);
        if (iconUri != null) {
        final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
        if (iconUri != null) {
            updateIcon(imageView, iconUri);
        } else {
            final Pair<Drawable, String> pair =
                    BluetoothUtils.getBtRainbowDrawableWithDescription(mContext, mCachedDevice);
            imageView.setImageDrawable(pair.first);
            imageView.setContentDescription(pair.second);
        }

        final int batteryLevel = BluetoothUtils.getIntMetaData(bluetoothDevice, batteryMetaKey);
        final boolean charging = BluetoothUtils.getBooleanMetaData(bluetoothDevice, chargeMetaKey);
        if (DEBUG) {
@@ -244,25 +265,36 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
                    + ", charge : " + chargeMetaKey + ", batteryLevel : " + batteryLevel
                    + ", charging : " + charging + ", iconUri : " + iconUri);
        }

        if (batteryId != CASE_DEVICE_ID) {
            showBatteryPredictionIfNecessary(linearLayout, batteryId, batteryLevel);
        if (deviceId == LEFT_DEVICE_ID || deviceId == RIGHT_DEVICE_ID) {
            showBatteryPredictionIfNecessary(linearLayout, deviceId, batteryLevel);
        }
        final TextView batterySummaryView = linearLayout.findViewById(R.id.bt_battery_summary);
        if (batteryLevel != BluetoothUtils.META_INT_ERROR) {
            linearLayout.setVisibility(View.VISIBLE);
            final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
            textView.setText(com.android.settings.Utils.formatPercentage(batteryLevel));
            textView.setVisibility(View.VISIBLE);
            batterySummaryView.setText(com.android.settings.Utils.formatPercentage(batteryLevel));
            batterySummaryView.setVisibility(View.VISIBLE);
            showBatteryIcon(linearLayout, batteryLevel, charging, batteryMetaKey);
        } else {
            if (deviceId == MAIN_DEVICE_ID) {
                linearLayout.setVisibility(View.VISIBLE);
                batterySummaryView.setText(com.android.settings.Utils.formatPercentage(
                        bluetoothDevice.getBatteryLevel()));
                batterySummaryView.setVisibility(View.VISIBLE);
                linearLayout.findViewById(R.id.bt_battery_icon).setVisibility(View.GONE);
            } else {
                // Hide it if it doesn't have battery information
                linearLayout.setVisibility(View.GONE);
            }
        }

        final TextView textView = linearLayout.findViewById(R.id.header_title);
        if (deviceId == MAIN_DEVICE_ID) {
            textView.setVisibility(View.GONE);
        } else {
            textView.setText(titleResId);
            textView.setVisibility(View.VISIBLE);
        }
    }

    private void showBatteryPredictionIfNecessary(LinearLayout linearLayout, int batteryId,
            int batteryLevel) {
+1 −8
Original line number Diff line number Diff line
@@ -16,10 +16,8 @@

package com.android.settings.bluetooth;

import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.Pair;

@@ -27,7 +25,6 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -56,11 +53,7 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController

    @Override
    public boolean isAvailable() {
        final boolean advancedEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
                SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, true);
        return !advancedEnabled
                || !BluetoothUtils.getBooleanMetaData(mCachedDevice.getDevice(),
                        BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
        return !Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice());
    }

    @Override
+36 −0
Original line number Diff line number Diff line
@@ -21,14 +21,18 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.DialogInterface;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;

import com.android.settings.R;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener;
@@ -153,4 +157,36 @@ public final class Utils {
        return Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1;
    }

    /**
     * Check if the Bluetooth device supports advanced details header
     *
     * @param bluetoothDevice the BluetoothDevice to get metadata
     * @return true if it supports advanced details header, false otherwise.
     */
    public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) {
        final boolean advancedEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
                SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, true);
        if (!advancedEnabled) {
            Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
            return false;
        }
        // The metadata is for Android R
        final boolean untetheredHeadset = BluetoothUtils.getBooleanMetaData(bluetoothDevice,
                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
        if (untetheredHeadset) {
            Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true");
            return true;
        }
        // The metadata is for Android S
        final String deviceType = BluetoothUtils.getStringMetaData(bluetoothDevice,
                BluetoothDevice.METADATA_DEVICE_TYPE);
        if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
                || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
                || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)) {
            Log.d(TAG, "isAdvancedDetailsHeader: deviceType is " + deviceType);
            return true;
        }
        return false;
    }
}
+56 −0
Original line number Diff line number Diff line
@@ -108,8 +108,61 @@ public class AdvancedBluetoothDetailsHeaderControllerTest {
        assertThat(iconDrawable.getCharging()).isTrue();
    }

    @Test
    public void refresh_connectedWatch_behaveAsExpected() {
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
                BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                String.valueOf(false).getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
                String.valueOf(BATTERY_LEVEL_MAIN).getBytes());
        when(mCachedDevice.isConnected()).thenReturn(true);

        mController.refresh();

        assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
                View.GONE);
        assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
                View.GONE);
        assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
                View.VISIBLE);
        assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_middle), BATTERY_LEVEL_MAIN);
    }

    @Test
    public void refresh_connectedUntetheredHeadset_behaveAsExpected() {
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
                BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                String.valueOf(false).getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
                String.valueOf(BATTERY_LEVEL_LEFT).getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
                String.valueOf(BATTERY_LEVEL_RIGHT).getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
                String.valueOf(BATTERY_LEVEL_MAIN).getBytes());
        when(mCachedDevice.isConnected()).thenReturn(true);

        mController.refresh();

        assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_left), BATTERY_LEVEL_LEFT);
        assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_right), BATTERY_LEVEL_RIGHT);
        assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_middle), BATTERY_LEVEL_MAIN);
    }

    @Test
    public void refresh_connected_updateCorrectInfo() {
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                String.valueOf(true).getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
                String.valueOf(BATTERY_LEVEL_LEFT).getBytes());
@@ -150,6 +203,9 @@ public class AdvancedBluetoothDetailsHeaderControllerTest {

    @Test
    public void refresh_withLowBatteryAndUncharged_showAlertIcon() {
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                String.valueOf(true).getBytes());
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
                String.valueOf(LOW_BATTERY_LEVEL).getBytes());