diff --git a/res/layout/advanced_bt_entity_header.xml b/res/layout/advanced_bt_entity_header.xml new file mode 100644 index 00000000000..4aadad1fc34 --- /dev/null +++ b/res/layout/advanced_bt_entity_header.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/advanced_bt_entity_sub.xml b/res/layout/advanced_bt_entity_sub.xml new file mode 100644 index 00000000000..07ea8146fad --- /dev/null +++ b/res/layout/advanced_bt_entity_sub.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index b773fd97ebf..f0809db9c04 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10513,6 +10513,13 @@ No Bluetooth devices + + Left + + Right + + Case + Settings Panel diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index cf9fbf936c7..f485c6a8709 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -23,7 +23,16 @@ android:key="bluetooth_device_header" android:layout="@layout/settings_entity_header" android:selectable="false" - settings:allowDividerBelow="true"/> + settings:allowDividerBelow="true" + settings:searchable="false"/> + + pair = BluetoothUtils .getBtClassDrawableWithDescription(mContext, mCachedDevice, - mContext.getResources().getFraction(R.fraction.bt_battery_scale_fraction, 1, 1)); + mContext.getResources().getFraction(R.fraction.bt_battery_scale_fraction, 1, + 1)); String summaryText = mCachedDevice.getConnectionSummary(); // If both the hearing aids are connected, two device status should be shown. // If Second Summary is unavailable, to set it to null. @@ -84,4 +85,4 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController public String getPreferenceKey() { return KEY_DEVICE_HEADER; } -} +} \ No newline at end of file diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java b/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java index 987dbe4f050..835961dee5e 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java @@ -62,4 +62,4 @@ public class BluetoothDetailsMacAddressController extends BluetoothDetailsContro } return mFooterPreference.getKey(); } -} +} \ No newline at end of file diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 88cebcb331f..5c609e69c44 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -264,4 +264,4 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll public String getPreferenceKey() { return KEY_PROFILES_GROUP; } -} +} \ No newline at end of file diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 7cff772c96b..98455f2f0aa 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -108,6 +108,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment mManager = getLocalBluetoothManager(context); mCachedDevice = getCachedDevice(mDeviceAddress); super.onAttach(context); + use(AdvancedBluetoothDetailsHeaderController.class).init(mCachedDevice); final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory( context).getBluetoothFeatureProvider(context); 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 new file mode 100644 index 00000000000..a74610e1a3d --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 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.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; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowEntityHeaderController.class) +public class AdvancedBluetoothDetailsHeaderControllerTest{ + 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() { + 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_MAIN); + assertThat(drawable).isInstanceOf(BatteryMeterView.BatteryMeterDrawable.class); + + final BatteryMeterView.BatteryMeterDrawable iconDrawable = + (BatteryMeterView.BatteryMeterDrawable) drawable; + 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/BluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java index 1e780753156..930d9cbe087 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java @@ -50,7 +50,6 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) - @Config(shadows = ShadowEntityHeaderController.class) public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsControllerTestBase { 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); + } + }