diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java index 2ecaf503fe1..4b8efd4e371 100644 --- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java @@ -16,17 +16,20 @@ package com.android.settings.bluetooth; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.VisibleForTesting; +import androidx.core.graphics.drawable.IconCompat; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.fuelgauge.BatteryMeterView; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -37,7 +40,8 @@ import com.android.settingslib.widget.LayoutPreference; */ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceController { - private LayoutPreference mLayoutPreference; + @VisibleForTesting + LayoutPreference mLayoutPreference; private CachedBluetoothDevice mCachedDevice; public AdvancedBluetoothDetailsHeaderController(Context context, String prefKey) { @@ -46,8 +50,9 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont @Override public int getAvailabilityStatus() { - //TODO(b/122460277): decide whether it is available by {@code bluetoothDevice} - return CONDITIONALLY_UNAVAILABLE; + final boolean unthetheredHeadset = Utils.getBooleanMetaData(mCachedDevice.getDevice(), + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET); + return unthetheredHeadset ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } @Override @@ -62,12 +67,28 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont mCachedDevice = cachedBluetoothDevice; } - private void refresh() { + @VisibleForTesting + void refresh() { if (mLayoutPreference != null && mCachedDevice != null) { final TextView title = mLayoutPreference.findViewById(R.id.entity_header_title); title.setText(mCachedDevice.getName()); final TextView summary = mLayoutPreference.findViewById(R.id.entity_header_summary); summary.setText(mCachedDevice.getConnectionSummary()); + + updateSubLayout(mLayoutPreference.findViewById(R.id.layout_left), + BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON, + BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY, + R.string.bluetooth_left_name); + + updateSubLayout(mLayoutPreference.findViewById(R.id.layout_middle), + BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON, + BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY, + R.string.bluetooth_middle_name); + + updateSubLayout(mLayoutPreference.findViewById(R.id.layout_right), + BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON, + BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY, + R.string.bluetooth_right_name); } } @@ -79,10 +100,35 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont drawable.setBatteryLevel(level); drawable.setShowPercent(false); drawable.setBatteryColorFilter(new PorterDuffColorFilter( - Utils.getColorAttrDefaultColor(context, android.R.attr.colorControlNormal), + com.android.settings.Utils.getColorAttrDefaultColor(context, + android.R.attr.colorControlNormal), PorterDuff.Mode.SRC_IN)); return drawable; } + private void updateSubLayout(LinearLayout linearLayout, int iconMetaKey, int batteryMetaKey, + int titleResId) { + if (linearLayout == null) { + return; + } + final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice(); + final String iconUri = Utils.getStringMetaData(bluetoothDevice, iconMetaKey); + if (iconUri != null) { + final ImageView imageView = linearLayout.findViewById(R.id.header_icon); + final IconCompat iconCompat = IconCompat.createWithContentUri(iconUri); + imageView.setImageBitmap(iconCompat.getBitmap()); + } + + final int batteryLevel = Utils.getIntMetaData(bluetoothDevice, batteryMetaKey); + if (batteryLevel != Utils.META_INT_ERROR) { + final ImageView imageView = linearLayout.findViewById(R.id.bt_battery_icon); + imageView.setImageDrawable(createBtBatteryIcon(mContext, batteryLevel)); + final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary); + textView.setText(com.android.settings.Utils.formatPercentage(batteryLevel)); + } + + final TextView textView = linearLayout.findViewById(R.id.header_title); + textView.setText(titleResId); + } } diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java index a7fae1441f9..af150527c4c 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java @@ -16,6 +16,7 @@ package com.android.settings.bluetooth; +import android.bluetooth.BluetoothDevice; import android.content.Context; import androidx.preference.PreferenceFragmentCompat; @@ -43,6 +44,13 @@ public class BluetoothDetailsButtonsController extends BluetoothDetailsControlle mIsConnected = device.isConnected(); } + @Override + public boolean isAvailable() { + final boolean unthetheredHeadset = Utils.getBooleanMetaData(mCachedDevice.getDevice(), + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET); + return !unthetheredHeadset; + } + private void onForgetButtonPressed() { ForgetDeviceDialogFragment fragment = ForgetDeviceDialogFragment.newInstance(mCachedDevice.getAddress()); diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java index d6e395e0c31..ff4a98fc5ce 100755 --- a/src/com/android/settings/bluetooth/Utils.java +++ b/src/com/android/settings/bluetooth/Utils.java @@ -47,6 +47,8 @@ public final class Utils { static final boolean V = BluetoothUtils.V; // verbose logging static final boolean D = BluetoothUtils.D; // regular logging + public static final int META_INT_ERROR = -1; + private Utils() { } @@ -152,4 +154,29 @@ public final class Utils { return Settings.Global.getInt(context.getContentResolver(), Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1; } + + public static boolean getBooleanMetaData(BluetoothDevice bluetoothDevice, int key) { + if (bluetoothDevice == null) { + return false; + } + return Boolean.parseBoolean(bluetoothDevice.getMetadata(key)); + } + + public static String getStringMetaData(BluetoothDevice bluetoothDevice, int key) { + if (bluetoothDevice == null) { + return null; + } + return bluetoothDevice.getMetadata(key); + } + + public static int getIntMetaData(BluetoothDevice bluetoothDevice, int key) { + if (bluetoothDevice == null) { + return META_INT_ERROR; + } + try { + return Integer.parseInt(bluetoothDevice.getMetadata(key)); + } catch (NumberFormatException e) { + return META_INT_ERROR; + } + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java index aa91e37adb4..a74610e1a3d 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java @@ -18,15 +18,27 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; import com.android.settings.fuelgauge.BatteryMeterView; import com.android.settings.testutils.shadow.ShadowEntityHeaderController; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.widget.LayoutPreference; 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.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -34,26 +46,82 @@ import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(shadows = ShadowEntityHeaderController.class) public class AdvancedBluetoothDetailsHeaderControllerTest{ - private static final int BATTERY_LEVEL = 30; + private static final int BATTERY_LEVEL_MAIN = 30; + private static final int BATTERY_LEVEL_LEFT = 25; + private static final int BATTERY_LEVEL_RIGHT = 45; private Context mContext; + + @Mock + private BluetoothDevice mBluetoothDevice; + @Mock + private CachedBluetoothDevice mCachedDevice; private AdvancedBluetoothDetailsHeaderController mController; + private LayoutPreference mLayoutPreference; @Before public void setUp() { - mContext = RuntimeEnvironment.application; + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; mController = new AdvancedBluetoothDetailsHeaderController(mContext, "pref_Key"); + mController.init(mCachedDevice); + mLayoutPreference = new LayoutPreference(mContext, + LayoutInflater.from(mContext).inflate(R.layout.advanced_bt_entity_header, null)); + mController.mLayoutPreference = mLayoutPreference; + when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice); } @Test public void createBatteryIcon_hasCorrectInfo() { - final Drawable drawable = mController.createBtBatteryIcon(mContext, BATTERY_LEVEL); + final Drawable drawable = mController.createBtBatteryIcon(mContext, BATTERY_LEVEL_MAIN); assertThat(drawable).isInstanceOf(BatteryMeterView.BatteryMeterDrawable.class); final BatteryMeterView.BatteryMeterDrawable iconDrawable = (BatteryMeterView.BatteryMeterDrawable) drawable; - assertThat(iconDrawable.getBatteryLevel()).isEqualTo(BATTERY_LEVEL); + assertThat(iconDrawable.getBatteryLevel()).isEqualTo(BATTERY_LEVEL_MAIN); + } + + @Test + public void refresh_updateCorrectInfo() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY)).thenReturn( + String.valueOf(BATTERY_LEVEL_LEFT)); + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY)).thenReturn( + String.valueOf(BATTERY_LEVEL_RIGHT)); + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY)).thenReturn( + String.valueOf(BATTERY_LEVEL_MAIN)); + 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 getAvailabilityStatus_unthetheredHeadset_returnAvailable() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)) + .thenReturn("true"); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_notUnthetheredHeadset_returnUnavailable() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)) + .thenReturn("false"); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) { + final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary); + assertThat(textView.getText().toString()).isEqualTo( + com.android.settings.Utils.formatPercentage(batteryLevel)); } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java index 1337f6640e1..51e559f8900 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java @@ -15,6 +15,8 @@ */ package com.android.settings.bluetooth; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -22,6 +24,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothDevice; import android.content.Context; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -40,23 +43,68 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class UtilsTest { + private static final String STRING_METADATA = "string_metadata"; + private static final String BOOL_METADATA = "true"; + private static final String INT_METADATA = "25"; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; + @Mock + private BluetoothDevice mBluetoothDevice; private MetricsFeatureProvider mMetricsFeatureProvider; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mMetricsFeatureProvider = FakeFeatureFactory.setupForTest().getMetricsFeatureProvider(); } @Test - public void testShowConnectingError_shouldLogBluetoothConnectError() { + public void showConnectingError_shouldLogBluetoothConnectError() { when(mContext.getString(anyInt(), anyString())).thenReturn("testMessage"); Utils.showConnectingError(mContext, "testName", mock(LocalBluetoothManager.class)); verify(mMetricsFeatureProvider).visible(eq(mContext), anyInt(), eq(MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR)); } + + @Test + public void getStringMetaData_hasMetaData_getCorrectMetaData() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON)).thenReturn(STRING_METADATA); + + assertThat(Utils.getStringMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON)).isEqualTo(STRING_METADATA); + } + + @Test + public void getIntMetaData_hasMetaData_getCorrectMetaData() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY)).thenReturn(INT_METADATA); + + assertThat(Utils.getIntMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY)) + .isEqualTo(Integer.parseInt(INT_METADATA)); + } + + @Test + public void getIntMetaData_invalidMetaData_getErrorCode() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY)).thenReturn(STRING_METADATA); + + assertThat(Utils.getIntMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON)).isEqualTo(Utils.META_INT_ERROR); + } + + @Test + public void getBooleanMetaData_hasMetaData_getCorrectMetaData() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn(BOOL_METADATA); + + assertThat(Utils.getBooleanMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).isEqualTo(true); + } + }