Merge "Fix following three issues in SD Card UX" into tm-qpr-dev
diff --git a/src/com/android/settings/accessibility/AccessibilityStatsLogUtils.java b/src/com/android/settings/accessibility/AccessibilityStatsLogUtils.java
index d8a887d..e78982a 100644
--- a/src/com/android/settings/accessibility/AccessibilityStatsLogUtils.java
+++ b/src/com/android/settings/accessibility/AccessibilityStatsLogUtils.java
@@ -28,6 +28,7 @@
 
 import android.content.ComponentName;
 
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.settings.core.instrumentation.SettingsStatsLog;
 
 /** Methods for logging accessibility states. */
@@ -112,4 +113,26 @@
                 return SettingsStatsLog.ACCESSIBILITY_TEXT_READING_OPTIONS_CHANGED__ENTRY_POINT__TEXT_READING_UNKNOWN_ENTRY;
         }
     }
+
+    /**
+     * Converts the entering page id where the hearing aid binding process starts for logging.
+     *
+     * @param pageId the entry page id where the hearing aid binding process starts
+     * @return the int value for logging mapped from some page ids defined in
+     * {@link SettingsStatsLog}
+     */
+    public static int convertToHearingAidInfoBondEntry(int pageId) {
+        switch (pageId) {
+            case SettingsStatsLog.SETTINGS_UICHANGED__PAGE_ID__SETTINGS_CONNECTED_DEVICE_CATEGORY:
+                return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__CONNECTED_DEVICES;
+            case SettingsStatsLog.SETTINGS_UICHANGED__PAGE_ID__DIALOG_ACCESSIBILITY_HEARINGAID:
+                return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__ACCESSIBILITY_HEARING_AIDS;
+            case SettingsStatsLog.SETTINGS_UICHANGED__PAGE_ID__DIALOG_ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER:
+                return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER;
+            case SettingsStatsLog.SETTINGS_UICHANGED__PAGE_ID__BLUETOOTH_FRAGMENT:
+                return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH;
+            default:
+                return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__PAGE_UNKNOWN;
+        }
+    }
 }
diff --git a/src/com/android/settings/accessibility/HearingAidDialogFragment.java b/src/com/android/settings/accessibility/HearingAidDialogFragment.java
index 107c56d..7f3230f 100644
--- a/src/com/android/settings/accessibility/HearingAidDialogFragment.java
+++ b/src/com/android/settings/accessibility/HearingAidDialogFragment.java
@@ -51,7 +51,7 @@
     private void launchBluetoothAddDeviceSetting() {
         new SubSettingLauncher(getActivity())
                 .setDestination(BluetoothPairingDetail.class.getName())
-                .setSourceMetricsCategory(SettingsEnums.ACCESSIBILITY)
+                .setSourceMetricsCategory(getMetricsCategory())
                 .launch();
     }
 }
diff --git a/src/com/android/settings/applications/AppStateAppBatteryUsageBridge.java b/src/com/android/settings/applications/AppStateAppBatteryUsageBridge.java
index 3674212..c0f96c8 100644
--- a/src/com/android/settings/applications/AppStateAppBatteryUsageBridge.java
+++ b/src/com/android/settings/applications/AppStateAppBatteryUsageBridge.java
@@ -22,6 +22,7 @@
 import android.os.Build;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
@@ -37,14 +38,21 @@
     private static final String TAG = AppStateAppBatteryUsageBridge.class.getSimpleName();
     static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
-    private final Context mContext;
-    private final AppOpsManager mAppOpsManager;
-    private final PowerAllowlistBackend mPowerAllowlistBackend;
+    @VisibleForTesting
+    Context mContext;
+    @VisibleForTesting
+    AppOpsManager mAppOpsManager;
+    @VisibleForTesting
+    PowerAllowlistBackend mPowerAllowlistBackend;
 
-    private static final int MODE_UNKNOWN = 0;
-    private static final int MODE_UNRESTRICTED = 1;
-    private static final int MODE_OPTIMIZED = 2;
-    private static final int MODE_RESTRICTED = 3;
+    @VisibleForTesting
+    static final int MODE_UNKNOWN = 0;
+    @VisibleForTesting
+    static final int MODE_UNRESTRICTED = 1;
+    @VisibleForTesting
+    static final int MODE_OPTIMIZED = 2;
+    @VisibleForTesting
+    static final int MODE_RESTRICTED = 3;
 
     @IntDef(
             prefix = {"MODE_"},
@@ -110,8 +118,9 @@
         return new AppBatteryUsageDetails(mode);
     }
 
+    @VisibleForTesting
     @OptimizationMode
-    private static int getAppBatteryUsageDetailsMode(AppEntry entry) {
+    static int getAppBatteryUsageDetailsMode(AppEntry entry) {
         if (entry == null || entry.extraInfo == null) {
             return MODE_UNKNOWN;
         }
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 018fda5..9a4d13c 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -1196,9 +1196,12 @@
                     rebuild(R.id.sort_order_alpha, true);
                 }
                 return;
-            } else if (mManageApplications.mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
+            }
+
+            if (mManageApplications.mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
                 logAppBatteryUsage(filterType);
             }
+
             rebuild();
         }
 
@@ -1673,7 +1676,7 @@
                     holder.setSummary(AppLocaleDetails.getSummary(mContext, entry));
                     break;
                 case LIST_TYPE_BATTERY_OPTIMIZATION:
-                    holder.setSummary(R.string.app_battery_usage_summary);
+                    holder.setSummary(null);
                     break;
                 default:
                     holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index 4598483..2f1fcf3 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
+import android.widget.ScrollView;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -123,6 +124,9 @@
         final TextView footerTitle2 = findViewById(R.id.footer_title_2);
         footerTitle1.setText(getFooterTitle1());
         footerTitle2.setText(getFooterTitle2());
+
+        final ScrollView scrollView = findViewById(R.id.sud_scroll_view);
+        scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
 
     @Override
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
index 89d923d..29066b8 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
@@ -53,6 +53,8 @@
     @VisibleForTesting
     AudioDeviceAttributes mAudioDevice;
 
+    private boolean mIsAvailable;
+
     public BluetoothDetailsSpatialAudioController(
             Context context,
             PreferenceFragmentCompat fragment,
@@ -61,16 +63,13 @@
         super(context, fragment, device, lifecycle);
         AudioManager audioManager = context.getSystemService(AudioManager.class);
         mSpatializer = audioManager.getSpatializer();
-        mAudioDevice = new AudioDeviceAttributes(
-                AudioDeviceAttributes.ROLE_OUTPUT,
-                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-                mCachedDevice.getAddress());
+        getAvailableDevice();
 
     }
 
     @Override
     public boolean isAvailable() {
-        return mSpatializer.isAvailableForDevice(mAudioDevice) ? true : false;
+        return mIsAvailable;
     }
 
     @Override
@@ -152,4 +151,52 @@
         pref.setOnPreferenceClickListener(this);
         return pref;
     }
+
+    private void getAvailableDevice() {
+        AudioDeviceAttributes a2dpDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                mCachedDevice.getAddress());
+        AudioDeviceAttributes bleHeadsetDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                mCachedDevice.getAddress());
+        AudioDeviceAttributes bleSpeakerDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                mCachedDevice.getAddress());
+        AudioDeviceAttributes bleBroadcastDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_BROADCAST,
+                mCachedDevice.getAddress());
+        AudioDeviceAttributes hearingAidDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_HEARING_AID,
+                mCachedDevice.getAddress());
+
+        mIsAvailable = true;
+        if (mSpatializer.isAvailableForDevice(bleHeadsetDevice)) {
+            mAudioDevice = bleHeadsetDevice;
+        } else if (mSpatializer.isAvailableForDevice(bleSpeakerDevice)) {
+            mAudioDevice = bleSpeakerDevice;
+        } else if (mSpatializer.isAvailableForDevice(bleBroadcastDevice)) {
+            mAudioDevice = bleBroadcastDevice;
+        } else if (mSpatializer.isAvailableForDevice(a2dpDevice)) {
+            mAudioDevice = a2dpDevice;
+        } else {
+            mIsAvailable = mSpatializer.isAvailableForDevice(hearingAidDevice);
+            mAudioDevice = hearingAidDevice;
+        }
+
+        Log.d(TAG, "getAvailableDevice() device : "
+                + mCachedDevice.getDevice().getAnonymizedAddress()
+                + ", type : " + mAudioDevice.getType()
+                + ", is available : " + mIsAvailable);
+    }
+
+    @VisibleForTesting
+    void setAvailableDevice(AudioDeviceAttributes audioDevice) {
+        mAudioDevice = audioDevice;
+        mIsAvailable = mSpatializer.isAvailableForDevice(audioDevice);
+    }
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
index 2e0e9b5..9a92783 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
@@ -29,8 +29,11 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityStatsLogUtils;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
 import com.android.settingslib.search.Indexable;
 import com.android.settingslib.widget.FooterPreference;
 
@@ -179,6 +182,13 @@
             // If one device is connected(bonded), then close this fragment.
             finish();
             return;
+        } else if (bondState == BluetoothDevice.BOND_BONDING) {
+            // Set the bond entry where binding process starts for logging hearing aid device info
+            final int pageId = FeatureFactory.getFactory(
+                    getContext()).getMetricsFeatureProvider().getAttribution(getActivity());
+            final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
+                    pageId);
+            HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
         }
         if (mSelectedDevice != null && cachedDevice != null) {
             BluetoothDevice device = cachedDevice.getDevice();
diff --git a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
index ac48217..60a100d 100644
--- a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
+++ b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
@@ -54,8 +54,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO(b/225117454): Need to update SettingsEnums later
-        return SettingsEnums.ACCESSIBILITY;
+        return SettingsEnums.DIALOG_ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER;
     }
 
     @NonNull
@@ -82,7 +81,7 @@
     private void positiveButtonListener() {
         new SubSettingLauncher(getActivity())
                 .setDestination(BluetoothPairingDetail.class.getName())
-                .setSourceMetricsCategory(SettingsEnums.ACCESSIBILITY)
+                .setSourceMetricsCategory(getMetricsCategory())
                 .launch();
     }
 }
diff --git a/src/com/android/settings/dream/DreamAdapter.java b/src/com/android/settings/dream/DreamAdapter.java
index cfee12e..b81d6b6 100644
--- a/src/com/android/settings/dream/DreamAdapter.java
+++ b/src/com/android/settings/dream/DreamAdapter.java
@@ -21,6 +21,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.VectorDrawable;
 import android.text.TextUtils;
+import android.util.SparseIntArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -41,10 +42,9 @@
  */
 public class DreamAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
     private final List<IDreamItem> mItemList;
-    @LayoutRes
-    private final int mLayoutRes;
     private int mLastSelectedPos = -1;
     private boolean mEnabled = true;
+    private SparseIntArray mLayouts = new SparseIntArray();
 
     /**
      * View holder for each {@link IDreamItem}.
@@ -83,16 +83,6 @@
                 mSummaryView.setVisibility(View.VISIBLE);
             }
 
-            final Drawable previewImage = item.getPreviewImage();
-            if (previewImage != null) {
-                mPreviewView.setImageDrawable(previewImage);
-                mPreviewView.setClipToOutline(true);
-                mPreviewPlaceholderView.setVisibility(View.GONE);
-            } else {
-                mPreviewView.setImageDrawable(null);
-                mPreviewPlaceholderView.setVisibility(View.VISIBLE);
-            }
-
             final Drawable icon = item.isActive()
                     ? mContext.getDrawable(R.drawable.ic_dream_check_circle)
                     : item.getIcon().mutate();
@@ -122,12 +112,24 @@
                 itemView.setClickable(true);
             }
 
-            mCustomizeButton.setOnClickListener(v -> item.onCustomizeClicked());
-            mCustomizeButton.setVisibility(
-                    item.allowCustomization() && mEnabled ? View.VISIBLE : View.GONE);
-            // This must be called AFTER itemView.setSelected above, in order to keep the
-            // customize button in an unselected state.
-            mCustomizeButton.setSelected(false);
+            if (item.viewType() != DreamItemViewTypes.NO_DREAM_ITEM) {
+                final Drawable previewImage = item.getPreviewImage();
+                if (previewImage != null) {
+                    mPreviewView.setImageDrawable(previewImage);
+                    mPreviewView.setClipToOutline(true);
+                    mPreviewPlaceholderView.setVisibility(View.GONE);
+                } else {
+                    mPreviewView.setImageDrawable(null);
+                    mPreviewPlaceholderView.setVisibility(View.VISIBLE);
+                }
+
+                mCustomizeButton.setOnClickListener(v -> item.onCustomizeClicked());
+                mCustomizeButton.setVisibility(
+                        item.allowCustomization() && mEnabled ? View.VISIBLE : View.GONE);
+                // This must be called AFTER itemView.setSelected above, in order to keep the
+                // customize button in an unselected state.
+                mCustomizeButton.setSelected(false);
+            }
 
             setEnabledStateOnViews(itemView, mEnabled);
         }
@@ -149,16 +151,22 @@
         }
     }
 
+    public DreamAdapter(SparseIntArray layouts, List<IDreamItem> itemList) {
+        mItemList = itemList;
+        mLayouts = layouts;
+    }
+
     public DreamAdapter(@LayoutRes int layoutRes, List<IDreamItem> itemList) {
         mItemList = itemList;
-        mLayoutRes = layoutRes;
+        mLayouts.append(DreamItemViewTypes.DREAM_ITEM, layoutRes);
     }
 
     @NonNull
     @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+                                                      @DreamItemViewTypes.ViewType int viewType) {
         View view = LayoutInflater.from(viewGroup.getContext())
-                .inflate(mLayoutRes, viewGroup, false);
+                .inflate(mLayouts.get(viewType), viewGroup, false);
         return new DreamViewHolder(view, viewGroup.getContext());
     }
 
@@ -168,6 +176,11 @@
     }
 
     @Override
+    public @DreamItemViewTypes.ViewType int getItemViewType(int position) {
+        return mItemList.get(position).viewType();
+    }
+
+    @Override
     public int getItemCount() {
         return mItemList.size();
     }
diff --git a/src/com/android/settings/dream/DreamItemViewTypes.java b/src/com/android/settings/dream/DreamItemViewTypes.java
new file mode 100644
index 0000000..b720242
--- /dev/null
+++ b/src/com/android/settings/dream/DreamItemViewTypes.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.dream;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class representing a dream item view types.
+ */
+public final class DreamItemViewTypes {
+
+    /**
+     * The default dream item layout
+     */
+    public static final int DREAM_ITEM = 0;
+
+    /**
+     * The dream item layout indicating no dream item selected.
+     */
+    public static final int NO_DREAM_ITEM = 1;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({DreamItemViewTypes.DREAM_ITEM, DreamItemViewTypes.NO_DREAM_ITEM})
+    public @interface ViewType {}
+}
diff --git a/src/com/android/settings/dream/IDreamItem.java b/src/com/android/settings/dream/IDreamItem.java
index 49c82be..911a3cf 100644
--- a/src/com/android/settings/dream/IDreamItem.java
+++ b/src/com/android/settings/dream/IDreamItem.java
@@ -67,4 +67,11 @@
     default boolean allowCustomization() {
         return false;
     }
+
+    /**
+     * Returns whether or not this item is the no screensaver item.
+     */
+    default @DreamItemViewTypes.ViewType int viewType() {
+        return DreamItemViewTypes.DREAM_ITEM;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java
index ef81247..1f0adcf 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java
@@ -22,7 +22,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothDevice;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.Spatializer;
 
@@ -57,6 +59,8 @@
     private Lifecycle mSpatialAudioLifecycle;
     @Mock
     private PreferenceCategory mProfilesContainer;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
 
     private BluetoothDetailsSpatialAudioController mController;
     private SwitchPreference mSpatialAudioPref;
@@ -70,6 +74,8 @@
         when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
         when(mAudioManager.getSpatializer()).thenReturn(mSpatializer);
         when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
+        when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS);
 
         mController = new BluetoothDetailsSpatialAudioController(mContext, mFragment,
                 mCachedDevice, mSpatialAudioLifecycle);
@@ -83,15 +89,85 @@
     }
 
     @Test
-    public void isAvailable_spatialAudioIsAvailable_returnsTrue() {
-        when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true);
+    public void isAvailable_spatialAudioSupportA2dpDevice_returnsTrue() {
+        AudioDeviceAttributes a2dpDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                MAC_ADDRESS);
+        when(mSpatializer.isAvailableForDevice(a2dpDevice)).thenReturn(true);
+
+        mController.setAvailableDevice(a2dpDevice);
+
         assertThat(mController.isAvailable()).isTrue();
+        assertThat(mController.mAudioDevice.getType())
+                .isEqualTo(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
     }
 
     @Test
-    public void isAvailable_spatialAudioIsNotAvailable_returnsFalse() {
-        when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(false);
+    public void isAvailable_spatialAudioSupportBleHeadsetDevice_returnsTrue() {
+        AudioDeviceAttributes bleHeadsetDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                MAC_ADDRESS);
+        when(mSpatializer.isAvailableForDevice(bleHeadsetDevice)).thenReturn(true);
+
+        mController.setAvailableDevice(bleHeadsetDevice);
+
+        assertThat(mController.isAvailable()).isTrue();
+        assertThat(mController.mAudioDevice.getType())
+                .isEqualTo(AudioDeviceInfo.TYPE_BLE_HEADSET);
+    }
+
+    @Test
+    public void isAvailable_spatialAudioSupportBleSpeakerDevice_returnsTrue() {
+        AudioDeviceAttributes bleSpeakerDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                MAC_ADDRESS);
+        when(mSpatializer.isAvailableForDevice(bleSpeakerDevice)).thenReturn(true);
+
+        mController.setAvailableDevice(bleSpeakerDevice);
+
+        assertThat(mController.isAvailable()).isTrue();
+        assertThat(mController.mAudioDevice.getType())
+                .isEqualTo(AudioDeviceInfo.TYPE_BLE_SPEAKER);
+    }
+
+    @Test
+    public void isAvailable_spatialAudioSupportBleBroadcastDevice_returnsTrue() {
+        AudioDeviceAttributes bleBroadcastDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_BROADCAST,
+                MAC_ADDRESS);
+        when(mSpatializer.isAvailableForDevice(bleBroadcastDevice)).thenReturn(true);
+
+        mController.setAvailableDevice(bleBroadcastDevice);
+
+        assertThat(mController.isAvailable()).isTrue();
+        assertThat(mController.mAudioDevice.getType())
+                .isEqualTo(AudioDeviceInfo.TYPE_BLE_BROADCAST);
+    }
+
+    @Test
+    public void isAvailable_spatialAudioSupportHearingAidDevice_returnsTrue() {
+        AudioDeviceAttributes hearingAidDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_HEARING_AID,
+                MAC_ADDRESS);
+        when(mSpatializer.isAvailableForDevice(hearingAidDevice)).thenReturn(true);
+
+        mController.setAvailableDevice(hearingAidDevice);
+
+        assertThat(mController.isAvailable()).isTrue();
+        assertThat(mController.mAudioDevice.getType())
+                .isEqualTo(AudioDeviceInfo.TYPE_HEARING_AID);
+    }
+
+    @Test
+    public void isAvailable_spatialAudioNotSupported_returnsFalse() {
         assertThat(mController.isAvailable()).isFalse();
+        assertThat(mController.mAudioDevice.getType())
+                .isEqualTo(AudioDeviceInfo.TYPE_HEARING_AID);
     }
 
     @Test
diff --git a/tests/unit/src/com/android/settings/applications/AppStateAppBatteryUsageBridgeTest.java b/tests/unit/src/com/android/settings/applications/AppStateAppBatteryUsageBridgeTest.java
new file mode 100644
index 0000000..c49b4cd
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/AppStateAppBatteryUsageBridgeTest.java
@@ -0,0 +1,78 @@
+package com.android.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public final class AppStateAppBatteryUsageBridgeTest {
+  private static final String TEST_PACKAGE_1 = "com.example.test.pkg1";
+  private static final String TEST_PACKAGE_2 = "com.example.test.pkg2";
+  private static final int UID_1 = 12345;
+  private static final int UID_2 = 7654321;
+
+  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+  private Context mContext;
+  @Mock
+  private AppOpsManager mAppOpsManager;
+  @Mock
+  private PowerAllowlistBackend mPowerAllowlistBackend;
+
+  @Before
+  public void initMocks() {
+      MockitoAnnotations.initMocks(this);
+  }
+
+  @Test
+  public void updateExtraInfo_updatesRestricted() {
+    when(mPowerAllowlistBackend.isAllowlisted(TEST_PACKAGE_1)).thenReturn(false);
+    when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+            UID_1, TEST_PACKAGE_1)).thenReturn(AppOpsManager.MODE_IGNORED);
+    AppStateAppBatteryUsageBridge bridge =
+            new AppStateAppBatteryUsageBridge(mContext, null, null);
+    bridge.mAppOpsManager = mAppOpsManager;
+    bridge.mPowerAllowlistBackend = mPowerAllowlistBackend;
+    AppEntry entry = new AppEntry(mContext, null, 0);
+
+    bridge.updateExtraInfo(entry, TEST_PACKAGE_1, UID_1);
+
+    assertThat(entry.extraInfo.getClass())
+            .isEqualTo(AppStateAppBatteryUsageBridge.AppBatteryUsageDetails.class);
+    assertThat(AppStateAppBatteryUsageBridge.getAppBatteryUsageDetailsMode(entry))
+            .isEqualTo(AppStateAppBatteryUsageBridge.MODE_RESTRICTED);
+  }
+
+  @Test
+  public void updateExtraInfo_updatesUnrestricted() {
+    when(mPowerAllowlistBackend.isAllowlisted(TEST_PACKAGE_1)).thenReturn(true);
+    when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+            UID_2, TEST_PACKAGE_2)).thenReturn(AppOpsManager.MODE_ALLOWED);
+    AppStateAppBatteryUsageBridge bridge =
+            new AppStateAppBatteryUsageBridge(mContext, null, null);
+    bridge.mAppOpsManager = mAppOpsManager;
+    bridge.mPowerAllowlistBackend = mPowerAllowlistBackend;
+    AppEntry entry = new AppEntry(mContext, null, 0);
+
+    bridge.updateExtraInfo(entry, TEST_PACKAGE_2, UID_2);
+
+    assertThat(entry.extraInfo.getClass())
+            .isEqualTo(AppStateAppBatteryUsageBridge.AppBatteryUsageDetails.class);
+    assertThat(AppStateAppBatteryUsageBridge.getAppBatteryUsageDetailsMode(entry))
+            .isEqualTo(AppStateAppBatteryUsageBridge.MODE_UNRESTRICTED);
+  }
+}