Add Hearable control slice in bluetooth device detail settings

Bug: 229048602
Test: make -j64 RunSettingsRoboTests
Change-Id: I850aaee9bf7518c9f9de065fbbd1eb4919fc62ee
diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml
index f6c0af6..b44a93d 100644
--- a/res/xml/bluetooth_device_details_fragment.xml
+++ b/res/xml/bluetooth_device_details_fragment.xml
@@ -47,6 +47,11 @@
         settings:allowDividerBelow="true"/>
 
     <com.android.settings.slices.SlicePreference
+        android:key="bt_extra_control"
+        settings:controller="com.android.settings.slices.SlicePreferenceController"
+        settings:allowDividerAbove="true"/>
+
+    <com.android.settings.slices.SlicePreference
         android:key="bt_device_slice"
         settings:controller="com.android.settings.slices.BlockingSlicePrefController"
         settings:allowDividerBelow="true"
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 6d443ee..fdf0f38 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -22,12 +22,18 @@
 import android.app.settings.SettingsEnums;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Bundle;
 import android.provider.DeviceConfig;
+import android.text.TextUtils;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -36,12 +42,14 @@
 import com.android.settings.dashboard.RestrictedDashboardFragment;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.slices.BlockingSlicePrefController;
+import com.android.settings.slices.SlicePreferenceController;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import java.util.ArrayList;
+import java.util.IllegalFormatException;
 import java.util.List;
 
 public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment {
@@ -61,6 +69,7 @@
     @VisibleForTesting
     interface TestDataFactory {
         CachedBluetoothDevice getDevice(String deviceAddress);
+
         LocalBluetoothManager getManager(Context context);
     }
 
@@ -127,6 +136,49 @@
         use(BlockingSlicePrefController.class).setSliceUri(sliceEnabled
                 ? featureProvider.getBluetoothDeviceSettingsUri(mCachedDevice.getDevice())
                 : null);
+        updateExtraControlUri(/* viewWidth */ 0);
+    }
+
+    private void updateExtraControlUri(int viewWidth) {
+        BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(
+                getContext()).getBluetoothFeatureProvider(getContext());
+        boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
+                SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true);
+        Uri controlUri = null;
+        String uri = featureProvider.getBluetoothDeviceControlUri(mCachedDevice.getDevice());
+        if (!TextUtils.isEmpty(uri)) {
+            try {
+                controlUri = Uri.parse(String.format(uri, viewWidth));
+            } catch (IllegalFormatException | NullPointerException exception) {
+                Log.d(TAG, "unable to parse uri");
+                controlUri = null;
+            }
+        }
+        use(SlicePreferenceController.class).setSliceUri(sliceEnabled ? controlUri : null);
+    }
+
+    private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
+            new ViewTreeObserver.OnGlobalLayoutListener() {
+                @Override
+                public void onGlobalLayout() {
+                    View view = getView();
+                    if (view == null) {
+                        return;
+                    }
+                    updateExtraControlUri(view.getWidth());
+                    view.getViewTreeObserver().removeOnGlobalLayoutListener(
+                            mOnGlobalLayoutListener);
+                }
+            };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        if (view != null) {
+            view.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
+        }
+        return view;
     }
 
     @Override
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
index 582a26c..51ef8e2 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
@@ -26,8 +26,17 @@
 
     /**
      * Get the {@link Uri} that represents extra settings for a specific bluetooth device
+     *
      * @param bluetoothDevice bluetooth device
      * @return {@link Uri} for extra settings
      */
     Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice);
+
+    /**
+     * Get the {@link Uri} that represents extra control for a specific bluetooth device
+     *
+     * @param bluetoothDevice bluetooth device
+     * @return {@link String} uri string for extra control
+     */
+    String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice);
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
index cd75951..04d3ec4 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.net.Uri;
 
+import com.android.settingslib.bluetooth.BluetoothUtils;
+
 /**
  * Impl of {@link BluetoothFeatureProvider}
  */
@@ -37,4 +39,9 @@
                 BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI);
         return uriByte == null ? null : Uri.parse(new String(uriByte));
     }
+
+    @Override
+    public String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice) {
+        return BluetoothUtils.getControlUriMetaData(bluetoothDevice);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java
index 9ab1d87..3d40bfc 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java
@@ -33,6 +33,11 @@
 @RunWith(RobolectricTestRunner.class)
 public class BluetoothFeatureProviderImplTest {
     private static final String SETTINGS_URI = "content://test.provider/settings_uri";
+    private static final String CONTROL_METADATA =
+            "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + SETTINGS_URI
+                    + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
+    private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+
     private BluetoothFeatureProvider mBluetoothFeatureProvider;
 
     @Mock
@@ -54,4 +59,13 @@
         final Uri uri = mBluetoothFeatureProvider.getBluetoothDeviceSettingsUri(mBluetoothDevice);
         assertThat(uri.toString()).isEqualTo(SETTINGS_URI);
     }
+
+    @Test
+    public void getBluetoothDeviceControlUri_returnsCorrectUri() {
+        when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)).thenReturn(
+                CONTROL_METADATA.getBytes());
+        assertThat(
+                mBluetoothFeatureProvider.getBluetoothDeviceControlUri(mBluetoothDevice)).isEqualTo(
+                SETTINGS_URI);
+    }
 }