Merge "Update the illustration of Double-tap to check phone page."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 290f84e..0279eec 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4349,6 +4349,7 @@
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES"/>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
 
diff --git a/res/layout/apps_filter_spinner.xml b/res/layout/apps_filter_spinner.xml
index 1de5705..fcdcb5e 100644
--- a/res/layout/apps_filter_spinner.xml
+++ b/res/layout/apps_filter_spinner.xml
@@ -21,13 +21,11 @@
     android:layout_height="wrap_content"
     android:background="@android:color/transparent">
 
-    <com.android.settingslib.widget.settingsspinner.SettingsSpinner
+    <Spinner
         android:id="@+id/filter_spinner"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:layout_centerHorizontal="true"
-        android:layout_marginTop="16dp"
-        android:layout_marginBottom="8dp"
         android:theme="@style/Widget.PopupWindow.Settings"/>
 
     <ImageView
diff --git a/res/layout/data_usage_cycles.xml b/res/layout/data_usage_cycles.xml
index 05c38e0..c957f9c 100644
--- a/res/layout/data_usage_cycles.xml
+++ b/res/layout/data_usage_cycles.xml
@@ -20,7 +20,7 @@
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight">
 
-    <com.android.settingslib.widget.settingsspinner.SettingsSpinner
+    <Spinner
         android:id="@+id/cycles_spinner"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
diff --git a/res/layout/manage_apps_filter_spinner.xml b/res/layout/manage_apps_filter_spinner.xml
index 8283bb8..a2d0e12 100644
--- a/res/layout/manage_apps_filter_spinner.xml
+++ b/res/layout/manage_apps_filter_spinner.xml
@@ -21,12 +21,10 @@
     android:layout_height="wrap_content"
     android:background="@android:color/transparent">
 
-    <com.android.settingslib.widget.settingsspinner.SettingsSpinner
+    <Spinner
         android:id="@+id/filter_spinner"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:layout_marginStart="24dp"
-        android:layout_marginTop="16dp"
-        android:layout_marginBottom="8dp"
         android:theme="@style/Widget.PopupWindow.Settings"/>
 </FrameLayout>
diff --git a/res/layout/tare_dropdown_page.xml b/res/layout/tare_dropdown_page.xml
index 79931e8..674b189 100644
--- a/res/layout/tare_dropdown_page.xml
+++ b/res/layout/tare_dropdown_page.xml
@@ -6,15 +6,14 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".development.tare.DropdownActivity">
-  <com.android.settingslib.widget.settingsspinner.SettingsSpinner
+  <Spinner
       android:id="@+id/spinner"
-      android:layout_width="match_parent"
+      android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
-      android:theme="@style/Widget.PopupWindow.Settings"
-      android:padding="10dp" />
+      android:theme="@style/Widget.PopupWindow.Settings" />
   <FrameLayout
       android:id="@+id/frame_layout"
       android:layout_width="match_parent"
diff --git a/res/layout/tare_homepage.xml b/res/layout/tare_homepage.xml
index 2c184d2..ddc9333 100644
--- a/res/layout/tare_homepage.xml
+++ b/res/layout/tare_homepage.xml
@@ -60,5 +60,6 @@
         android:layout_height="wrap_content"
         android:layout_margin="20dp"
         android:onClick="revertSettings"
-        android:text="@string/tare_revert" />
+        android:text="@string/tare_revert"
+        style="@style/ActionPrimaryButton" />
 </LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9d868cb..ff14d1d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -13939,4 +13939,13 @@
 
     <!-- Text to explain an activity is a temporary placeholder [CHAR LIMIT=none] -->
     <string name="placeholder_activity" translatable="false">*This is a temporary placeholder fallback activity.</string>
+
+    <!-- The title of the spatial audio [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_spatial_audio_title">Spatial audio</string>
+    <!-- The summary of the spatial audio [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_spatial_audio_summary">Immersive audio seems like it\u0027s coming from all around you. Only works with some media.</string>
+    <!-- The title of the head tracking [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_head_tracking_title">Make audio more realistic</string>
+    <!-- The summary of the head tracking [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_head_tracking_summary">Shift positioning of audio so it sounds more natural.</string>
 </resources>
diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml
index 9df1955..b21d5c9 100644
--- a/res/xml/bluetooth_device_details_fragment.xml
+++ b/res/xml/bluetooth_device_details_fragment.xml
@@ -53,6 +53,9 @@
         android:key="device_companion_apps"/>
 
     <PreferenceCategory
+        android:key="spatial_audio_group"/>
+
+    <PreferenceCategory
         android:key="bluetooth_profiles"/>
 
     <com.android.settingslib.widget.FooterPreference
diff --git a/src/com/android/settings/applications/ProcessStatsBase.java b/src/com/android/settings/applications/ProcessStatsBase.java
index 67fc4c1..ce1453f 100644
--- a/src/com/android/settings/applications/ProcessStatsBase.java
+++ b/src/com/android/settings/applications/ProcessStatsBase.java
@@ -30,7 +30,7 @@
 import com.android.settings.applications.ProcStatsData.MemInfo;
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settingslib.core.instrumentation.Instrumentable;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter;
+import com.android.settingslib.widget.SettingsSpinnerAdapter;
 
 public abstract class ProcessStatsBase extends SettingsPreferenceFragment
         implements OnItemSelectedListener {
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 0c12706..a6ce6fb 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -135,7 +135,7 @@
 import com.android.settingslib.applications.ApplicationsState.VolumeFilter;
 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
 import com.android.settingslib.utils.ThreadUtils;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter;
+import com.android.settingslib.widget.SettingsSpinnerAdapter;
 
 import com.google.android.material.appbar.AppBarLayout;
 
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
new file mode 100644
index 0000000..89d923d
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
@@ -0,0 +1,155 @@
+/*
+ * 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.bluetooth;
+
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.Spatializer;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * The controller of the Spatial audio setting in the bluetooth detail settings.
+ */
+public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsController
+        implements Preference.OnPreferenceClickListener {
+
+    private static final String TAG = "BluetoothSpatialAudioController";
+    private static final String KEY_SPATIAL_AUDIO_GROUP = "spatial_audio_group";
+    private static final String KEY_SPATIAL_AUDIO = "spatial_audio";
+    private static final String KEY_HEAD_TRACKING = "head_tracking";
+
+    private final Spatializer mSpatializer;
+
+    @VisibleForTesting
+    PreferenceCategory mProfilesContainer;
+    @VisibleForTesting
+    AudioDeviceAttributes mAudioDevice;
+
+    public BluetoothDetailsSpatialAudioController(
+            Context context,
+            PreferenceFragmentCompat fragment,
+            CachedBluetoothDevice device,
+            Lifecycle lifecycle) {
+        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());
+
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mSpatializer.isAvailableForDevice(mAudioDevice) ? true : false;
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        SwitchPreference switchPreference = (SwitchPreference) preference;
+        String key = switchPreference.getKey();
+        if (TextUtils.equals(key, KEY_SPATIAL_AUDIO)) {
+            if (switchPreference.isChecked()) {
+                mSpatializer.addCompatibleAudioDevice(mAudioDevice);
+            } else {
+                mSpatializer.removeCompatibleAudioDevice(mAudioDevice);
+            }
+            refresh();
+            return true;
+        } else if (TextUtils.equals(key, KEY_HEAD_TRACKING)) {
+            mSpatializer.setHeadTrackerEnabled(switchPreference.isChecked(), mAudioDevice);
+            return true;
+        } else {
+            Log.w(TAG, "invalid key name.");
+            return false;
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SPATIAL_AUDIO_GROUP;
+    }
+
+    @Override
+    protected void init(PreferenceScreen screen) {
+        mProfilesContainer = screen.findPreference(getPreferenceKey());
+        mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category);
+        refresh();
+    }
+
+    @Override
+    protected void refresh() {
+        SwitchPreference spatialAudioPref = mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO);
+        if (spatialAudioPref == null) {
+            spatialAudioPref = createSpatialAudioPreference(mProfilesContainer.getContext());
+            mProfilesContainer.addPreference(spatialAudioPref);
+        }
+
+        boolean isSpatialAudioOn = mSpatializer.getCompatibleAudioDevices().contains(mAudioDevice);
+        Log.d(TAG, "refresh() isSpatialAudioOn : " + isSpatialAudioOn);
+        spatialAudioPref.setChecked(isSpatialAudioOn);
+
+        SwitchPreference headTrackingPref = mProfilesContainer.findPreference(KEY_HEAD_TRACKING);
+        if (headTrackingPref == null) {
+            headTrackingPref = createHeadTrackingPreference(mProfilesContainer.getContext());
+            mProfilesContainer.addPreference(headTrackingPref);
+        }
+
+        boolean isHeadTrackingAvailable =
+                isSpatialAudioOn && mSpatializer.hasHeadTracker(mAudioDevice);
+        Log.d(TAG, "refresh() has head tracker : " + mSpatializer.hasHeadTracker(mAudioDevice));
+        headTrackingPref.setVisible(isHeadTrackingAvailable);
+        if (isHeadTrackingAvailable) {
+            headTrackingPref.setChecked(mSpatializer.isHeadTrackerEnabled(mAudioDevice));
+        }
+    }
+
+    @VisibleForTesting
+    SwitchPreference createSpatialAudioPreference(Context context) {
+        SwitchPreference pref = new SwitchPreference(context);
+        pref.setKey(KEY_SPATIAL_AUDIO);
+        pref.setTitle(context.getString(R.string.bluetooth_details_spatial_audio_title));
+        pref.setSummary(context.getString(R.string.bluetooth_details_spatial_audio_summary));
+        pref.setOnPreferenceClickListener(this);
+        return pref;
+    }
+
+    @VisibleForTesting
+    SwitchPreference createHeadTrackingPreference(Context context) {
+        SwitchPreference pref = new SwitchPreference(context);
+        pref.setKey(KEY_HEAD_TRACKING);
+        pref.setTitle(context.getString(R.string.bluetooth_details_head_tracking_title));
+        pref.setSummary(context.getString(R.string.bluetooth_details_head_tracking_summary));
+        pref.setOnPreferenceClickListener(this);
+        return pref;
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 4980ba3..6532482 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -187,6 +187,8 @@
                     lifecycle));
             controllers.add(new BluetoothDetailsCompanionAppsController(context, this,
                     mCachedDevice, lifecycle));
+            controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
+                    lifecycle));
             controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
                     mCachedDevice, lifecycle));
             controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java
index 1292d00..2cabd8d 100644
--- a/src/com/android/settings/datausage/CycleAdapter.java
+++ b/src/com/android/settings/datausage/CycleAdapter.java
@@ -27,7 +27,7 @@
 import com.android.settings.Utils;
 import com.android.settingslib.net.ChartData;
 import com.android.settingslib.net.NetworkCycleData;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter;
+import com.android.settingslib.widget.SettingsSpinnerAdapter;
 
 import java.time.ZonedDateTime;
 import java.util.Iterator;
diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java
index 67298a1..867930b 100644
--- a/src/com/android/settings/datausage/SpinnerPreference.java
+++ b/src/com/android/settings/datausage/SpinnerPreference.java
@@ -18,12 +18,12 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.AdapterView;
+import android.widget.Spinner;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinner;
 
 public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface {
 
@@ -63,7 +63,7 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-        SettingsSpinner spinner = (SettingsSpinner) holder.findViewById(R.id.cycles_spinner);
+        Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner);
         spinner.setAdapter(mAdapter);
         spinner.setSelection(mPosition);
         spinner.setOnItemSelectedListener(mOnSelectedListener);
diff --git a/src/com/android/settings/development/tare/DropdownActivity.java b/src/com/android/settings/development/tare/DropdownActivity.java
index c1a11fa..55f1fec 100644
--- a/src/com/android/settings/development/tare/DropdownActivity.java
+++ b/src/com/android/settings/development/tare/DropdownActivity.java
@@ -26,6 +26,7 @@
 import android.widget.Spinner;
 
 import com.android.settings.R;
+import com.android.settingslib.widget.SettingsSpinnerAdapter;
 
 /**
  * Dropdown activity to allow for the user to easily switch between the different TARE
@@ -58,9 +59,8 @@
 
         String[] policies = getResources().getStringArray(R.array.tare_policies);
 
-        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(DropdownActivity.this,
-                android.R.layout.simple_list_item_1, policies);
-        arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        ArrayAdapter<String> arrayAdapter = new SettingsSpinnerAdapter<String>(this);
+        arrayAdapter.addAll(policies);
         mSpinner.setAdapter(arrayAdapter);
 
         mSpinner.setSelection(policy);
diff --git a/src/com/android/settings/deviceinfo/storage/StorageSelectionPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageSelectionPreferenceController.java
index 5d5a2a5..d972575 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageSelectionPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageSelectionPreferenceController.java
@@ -26,8 +26,8 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.SettingsSpinnerAdapter;
 import com.android.settingslib.widget.SettingsSpinnerPreference;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
index 71e65bf..436cde8 100644
--- a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
@@ -112,7 +112,8 @@
         if (intent != null && mBatteryListener != null) {
             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                 final String batteryLevel = Utils.getBatteryPercentage(intent);
-                final String batteryStatus = Utils.getBatteryStatus(mContext, intent);
+                final String batteryStatus =
+                        Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false);
                 final int batteryHealth = intent.getIntExtra(
                         BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
                 if (!Utils.isBatteryPresent(intent)) {
diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java
index 4e90710..98f19fe 100644
--- a/src/com/android/settings/fuelgauge/BatteryInfo.java
+++ b/src/com/android/settings/fuelgauge/BatteryInfo.java
@@ -37,7 +37,6 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.UsageView;
 import com.android.settingslib.R;
-import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.fuelgauge.EstimateKt;
 import com.android.settingslib.utils.PowerUtil;
@@ -244,6 +243,8 @@
             @NonNull BatteryUsageStats batteryUsageStats, Estimate estimate,
             long elapsedRealtimeUs, boolean shortString) {
         final long startTime = System.currentTimeMillis();
+        final boolean isCompactStatus = context.getResources().getBoolean(
+                com.android.settings.R.bool.config_use_compact_battery_status);
         BatteryInfo info = new BatteryInfo();
         info.mBatteryUsageStats = batteryUsageStats;
         info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast);
@@ -254,21 +255,21 @@
                 BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN)
                 == BatteryManager.BATTERY_HEALTH_OVERHEAT;
 
-        info.statusLabel = getBatteryStatus(context, batteryBroadcast);
+        info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast, isCompactStatus);
         info.batteryStatus = batteryBroadcast.getIntExtra(
                 BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
         if (!info.mCharging) {
             updateBatteryInfoDischarging(context, shortString, estimate, info);
         } else {
             updateBatteryInfoCharging(context, batteryBroadcast, batteryUsageStats,
-                    info);
+                    info, isCompactStatus);
         }
         BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", startTime);
         return info;
     }
 
     private static void updateBatteryInfoCharging(Context context, Intent batteryBroadcast,
-            BatteryUsageStats stats, BatteryInfo info) {
+            BatteryUsageStats stats, BatteryInfo info, boolean compactStatus) {
         final Resources resources = context.getResources();
         final long chargeTimeMs = stats.getChargeTimeRemainingMs();
         final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
@@ -292,7 +293,8 @@
                     R.string.power_remaining_charging_duration_only, timeString);
             info.chargeLabel = context.getString(resId, info.batteryPercentString, timeString);
         } else {
-            final String chargeStatusLabel = getBatteryStatus(context, batteryBroadcast);
+            final String chargeStatusLabel =
+                    Utils.getBatteryStatus(context, batteryBroadcast, compactStatus);
             info.remainingLabel = null;
             info.chargeLabel = info.batteryLevel == 100 ? info.batteryPercentString :
                     resources.getString(R.string.power_charging, info.batteryPercentString,
@@ -326,35 +328,6 @@
         }
     }
 
-    private static String getBatteryStatus(Context context, Intent batteryChangedIntent) {
-        final Resources res = context.getResources();
-        final boolean isShortStatus =
-                res.getBoolean(com.android.settings.R.bool.config_use_compact_battery_status);
-
-        if (!isShortStatus) {
-            return Utils.getBatteryStatus(context, batteryChangedIntent);
-        }
-
-        final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_UNKNOWN);
-        final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
-        String statusString = res.getString(R.string.battery_info_status_unknown);
-
-        if (batteryStatus.isCharged()) {
-            statusString = res.getString(R.string.battery_info_status_full);
-        } else {
-            if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
-                statusString = res.getString(R.string.battery_info_status_charging);
-            } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
-                statusString = res.getString(R.string.battery_info_status_discharging);
-            } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
-                statusString = res.getString(R.string.battery_info_status_not_charging);
-            }
-        }
-
-        return statusString;
-    }
-
     public interface BatteryDataParser {
         void onParsingStarted(long startTime, long endTime);
 
diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
index 0fd0c0d..bdc52ad 100644
--- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
+++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.safetycenter;
 
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES;
 import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS;
 
 import android.content.BroadcastReceiver;
@@ -24,6 +26,8 @@
 
 import com.google.common.collect.ImmutableList;
 
+import java.util.List;
+
 /** Broadcast receiver for handling requests from Safety Center for fresh data. */
 public class SafetySourceBroadcastReceiver extends BroadcastReceiver {
 
@@ -33,17 +37,33 @@
             return;
         }
 
-        String[] sourceIdsExtra = intent.getStringArrayExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS);
-        if (sourceIdsExtra != null && sourceIdsExtra.length > 0) {
-            ImmutableList<String> sourceIds = ImmutableList.copyOf(sourceIdsExtra);
-
-            if (sourceIds.contains(LockScreenSafetySource.SAFETY_SOURCE_ID)) {
-                LockScreenSafetySource.sendSafetyData(context);
+        if (ACTION_REFRESH_SAFETY_SOURCES.equals(intent.getAction())) {
+            String[] sourceIdsExtra =
+                    intent.getStringArrayExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS);
+            if (sourceIdsExtra != null && sourceIdsExtra.length > 0) {
+                refreshSafetySources(context, ImmutableList.copyOf(sourceIdsExtra));
             }
-
-            if (sourceIds.contains(BiometricsSafetySource.SAFETY_SOURCE_ID)) {
-                BiometricsSafetySource.sendSafetyData(context);
-            }
+            return;
         }
+
+
+        if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+            refreshAllSafetySources(context);
+        }
+    }
+
+    private static void refreshSafetySources(Context context, List<String> sourceIds) {
+        if (sourceIds.contains(LockScreenSafetySource.SAFETY_SOURCE_ID)) {
+            LockScreenSafetySource.sendSafetyData(context);
+        }
+
+        if (sourceIds.contains(BiometricsSafetySource.SAFETY_SOURCE_ID)) {
+            BiometricsSafetySource.sendSafetyData(context);
+        }
+    }
+
+    private static void refreshAllSafetySources(Context context) {
+        LockScreenSafetySource.sendSafetyData(context);
+        BiometricsSafetySource.sendSafetyData(context);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java
new file mode 100644
index 0000000..ef81247
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+import android.media.Spatializer;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.SwitchPreference;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+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 java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetailsControllerTestBase {
+
+    private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
+    private static final String KEY_SPATIAL_AUDIO = "spatial_audio";
+    private static final String KEY_HEAD_TRACKING = "head_tracking";
+
+    @Mock
+    private AudioManager mAudioManager;
+    @Mock
+    private Spatializer mSpatializer;
+    @Mock
+    private Lifecycle mSpatialAudioLifecycle;
+    @Mock
+    private PreferenceCategory mProfilesContainer;
+
+    private BluetoothDetailsSpatialAudioController mController;
+    private SwitchPreference mSpatialAudioPref;
+    private SwitchPreference mHeadTrackingPref;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+        when(mAudioManager.getSpatializer()).thenReturn(mSpatializer);
+        when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
+
+        mController = new BluetoothDetailsSpatialAudioController(mContext, mFragment,
+                mCachedDevice, mSpatialAudioLifecycle);
+        mController.mProfilesContainer = mProfilesContainer;
+
+        mSpatialAudioPref = mController.createSpatialAudioPreference(mContext);
+        mHeadTrackingPref = mController.createHeadTrackingPreference(mContext);
+
+        when(mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO)).thenReturn(mSpatialAudioPref);
+        when(mProfilesContainer.findPreference(KEY_HEAD_TRACKING)).thenReturn(mHeadTrackingPref);
+    }
+
+    @Test
+    public void isAvailable_spatialAudioIsAvailable_returnsTrue() {
+        when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(true);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_spatialAudioIsNotAvailable_returnsFalse() {
+        when(mSpatializer.isAvailableForDevice(mController.mAudioDevice)).thenReturn(false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void refresh_spatialAudioIsTurnedOn_checksSpatialAudioPreference() {
+        List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
+        compatibleAudioDevices.add(mController.mAudioDevice);
+        when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
+
+        mController.refresh();
+
+        assertThat(mSpatialAudioPref.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refresh_spatialAudioIsTurnedOff_unchecksSpatialAudioPreference() {
+        List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
+        when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
+
+        mController.refresh();
+
+        assertThat(mSpatialAudioPref.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refresh_spatialAudioOnAndHeadTrackingIsAvailable_showsHeadTrackingPreference() {
+        List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
+        compatibleAudioDevices.add(mController.mAudioDevice);
+        when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
+        when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true);
+
+        mController.refresh();
+
+        assertThat(mHeadTrackingPref.isVisible()).isTrue();
+    }
+
+    @Test
+    public void
+            refresh_spatialAudioOnAndHeadTrackingIsNotAvailable_hidesHeadTrackingPreference() {
+        List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
+        compatibleAudioDevices.add(mController.mAudioDevice);
+        when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
+        when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(false);
+
+        mController.refresh();
+
+        assertThat(mHeadTrackingPref.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refresh_spatialAudioOff_hidesHeadTrackingPreference() {
+        List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
+        when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
+
+        mController.refresh();
+
+        assertThat(mHeadTrackingPref.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refresh_headTrackingIsTurnedOn_checksHeadTrackingPreference() {
+        List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
+        compatibleAudioDevices.add(mController.mAudioDevice);
+        when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
+        when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true);
+        when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(true);
+
+        mController.refresh();
+
+        assertThat(mHeadTrackingPref.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refresh_headTrackingIsTurnedOff_unchecksHeadTrackingPreference() {
+        List<AudioDeviceAttributes> compatibleAudioDevices = new ArrayList<>();
+        compatibleAudioDevices.add(mController.mAudioDevice);
+        when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices);
+        when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(true);
+        when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(false);
+
+        mController.refresh();
+
+        assertThat(mHeadTrackingPref.isChecked()).isFalse();
+    }
+
+    @Test
+    public void turnedOnSpatialAudio_invokesAddCompatibleAudioDevice() {
+        mSpatialAudioPref.setChecked(true);
+        mController.onPreferenceClick(mSpatialAudioPref);
+        verify(mSpatializer).addCompatibleAudioDevice(mController.mAudioDevice);
+    }
+
+    @Test
+    public void turnedOffSpatialAudio_invokesRemoveCompatibleAudioDevice() {
+        mSpatialAudioPref.setChecked(false);
+        mController.onPreferenceClick(mSpatialAudioPref);
+        verify(mSpatializer).removeCompatibleAudioDevice(mController.mAudioDevice);
+    }
+
+    @Test
+    public void turnedOnHeadTracking_invokesSetHeadTrackerEnabled_setsTrue() {
+        mHeadTrackingPref.setChecked(true);
+        mController.onPreferenceClick(mHeadTrackingPref);
+        verify(mSpatializer).setHeadTrackerEnabled(true, mController.mAudioDevice);
+    }
+
+    @Test
+    public void turnedOffHeadTracking_invokesSetHeadTrackerEnabled_setsFalse() {
+        mHeadTrackingPref.setChecked(false);
+        mController.onPreferenceClick(mHeadTrackingPref);
+        verify(mSpatializer).setHeadTrackerEnabled(false, mController.mAudioDevice);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
index 5f08698..d446930 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
@@ -85,8 +85,8 @@
 
         assertThat(mBatteryBroadcastReceiver.mBatteryLevel)
                 .isEqualTo(Utils.getBatteryPercentage(mChargingIntent));
-        assertThat(mBatteryBroadcastReceiver.mBatteryStatus)
-                .isEqualTo(Utils.getBatteryStatus(mContext, mChargingIntent));
+        assertThat(mBatteryBroadcastReceiver.mBatteryStatus).isEqualTo(
+                Utils.getBatteryStatus(mContext, mChargingIntent, /* compactStatus= */ false));
         verify(mBatteryListener).onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL);
     }
 
@@ -134,7 +134,7 @@
     public void testOnReceive_batteryDataNotChanged_listenerNotInvoked() {
         final String batteryLevel = Utils.getBatteryPercentage(mChargingIntent);
         final String batteryStatus =
-                Utils.getBatteryStatus(mContext, mChargingIntent);
+                Utils.getBatteryStatus(mContext, mChargingIntent, /* compactStatus= */ false);
         mBatteryBroadcastReceiver.mBatteryLevel = batteryLevel;
         mBatteryBroadcastReceiver.mBatteryStatus = batteryStatus;
 
@@ -159,8 +159,8 @@
 
         assertThat(mBatteryBroadcastReceiver.mBatteryLevel)
                 .isEqualTo(Utils.getBatteryPercentage(mChargingIntent));
-        assertThat(mBatteryBroadcastReceiver.mBatteryStatus)
-                .isEqualTo(Utils.getBatteryStatus(mContext, mChargingIntent));
+        assertThat(mBatteryBroadcastReceiver.mBatteryStatus).isEqualTo(
+                Utils.getBatteryStatus(mContext, mChargingIntent, /* compactStatus= */ false));
         assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
                 .isEqualTo(BatteryManager.BATTERY_HEALTH_UNKNOWN);
         // 2 times because register will force update the battery
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
index 8806e50..6c9addd 100644
--- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.safetycenter;
 
+import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES;
 import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -67,12 +68,24 @@
     }
 
     @Test
+    public void sendSafetyData_whenSafetyCenterIsEnabled_withNoIntentAction_sendsNoData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        Intent intent = new Intent().putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[]{});
+
+        new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+
+    @Test
     public void sendSafetyData_whenSafetyCenterIsDisabled_sendsNoData() {
         when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(false);
         Intent intent =
-                new Intent().putExtra(
-                        EXTRA_REFRESH_SAFETY_SOURCE_IDS,
-                        new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID });
+                new Intent()
+                        .setAction(ACTION_REFRESH_SAFETY_SOURCES)
+                        .putExtra(
+                                EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+                                new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID });
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
 
@@ -82,7 +95,7 @@
     @Test
     public void sendSafetyData_whenSafetyCenterIsEnabled_withNullSourceIds_sendsNoData() {
         when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
-        Intent intent = new Intent();
+        Intent intent = new Intent().setAction(ACTION_REFRESH_SAFETY_SOURCES);
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
 
@@ -92,7 +105,10 @@
     @Test
     public void sendSafetyData_whenSafetyCenterIsEnabled_withNoSourceIds_sendsNoData() {
         when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
-        Intent intent = new Intent().putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[]{});
+        Intent intent =
+                new Intent()
+                        .setAction(ACTION_REFRESH_SAFETY_SOURCES)
+                        .putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[]{});
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
 
@@ -103,9 +119,11 @@
     public void sendSafetyData_withLockscreenSourceId_sendsLockscreenData() {
         when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent =
-                new Intent().putExtra(
-                        EXTRA_REFRESH_SAFETY_SOURCE_IDS,
-                        new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID });
+                new Intent()
+                        .setAction(ACTION_REFRESH_SAFETY_SOURCES)
+                        .putExtra(
+                                EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+                                new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID });
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
         ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
@@ -120,13 +138,31 @@
     public void sendSafetyData_withBiometricsSourceId_sendsBiometricData() {
         when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent =
-                new Intent().putExtra(
-                        EXTRA_REFRESH_SAFETY_SOURCE_IDS,
-                        new String[]{ BiometricsSafetySource.SAFETY_SOURCE_ID });
+                new Intent()
+                        .setAction(ACTION_REFRESH_SAFETY_SOURCES)
+                        .putExtra(
+                                EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+                                new String[]{ BiometricsSafetySource.SAFETY_SOURCE_ID });
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
 
         // TODO(b/215517420): Update this test when BiometricSafetySource is implemented.
         verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
     }
+
+    @Test
+    public void sendSafetyData_onBootCompleted_sendsBiometricAndLockscreenData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        Intent intent = new Intent().setAction(Intent.ACTION_BOOT_COMPLETED);
+
+        // TODO(b/215517420): Update this test when BiometricSafetySource is implemented to test
+        // that biometrics data is also sent.
+        new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper, times(1))
+                .sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+
+        assertThat(safetySourceData.getId()).isEqualTo(LockScreenSafetySource.SAFETY_SOURCE_ID);
+    }
 }