Merge "Show restoring toast message only after Play Store responded successfully" into main
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 041dd95..432900e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11283,7 +11283,7 @@
     <string name="mobile_network_sim_name_rename">Save</string>
     <!-- Label for the on position of a switch on the mobile network details page which allows
          disabling/enabling a SIM. The SIM is enabled in this state. [CHAR LIMIT=40] -->
-    <string name="mobile_network_use_sim_on">Use SIM</string>
+    <string name="mobile_network_use_sim_on">Use this SIM</string>
     <!-- Label for the off position of a switch on the mobile network details page which allows
          disabling/enabling a SIM. The SIM is disabled in this state. [CHAR LIMIT=40] -->
     <string name="mobile_network_use_sim_off">Off</string>
@@ -11426,6 +11426,56 @@
     <!-- Body text of DSDS activation failure dialog. Users could toggle the selected SIM again or reboot to recover. [CHAR LIMIT=NONE] -->
     <string name="dsds_activation_failure_body_msg2">Try turning on the SIM again. If the problem continues, restart your device.</string>
 
+    <!-- Strings for dual SIM onboarding -->
+    <!-- Title of dual sim onboarding's bottom sheets. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_bottomsheets_title">Set up your SIM</string>
+    <!-- Body text of dual sim onboarding's bottom sheets. [CHAR LIMIT=NONE] -->
+    <string name="sim_onboarding_bottomsheets_msg">Set your mobile network preferences to use multiple SIMs on this device</string>
+    <!-- Title of dual sim onboarding's label sim page. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_label_sim_title">Label your SIMs</string>
+    <!-- Body text of dual sim onboarding's label sim page. [CHAR LIMIT=NONE] -->
+    <string name="sim_onboarding_label_sim_msg">You’ll see these labels when making calls, sending texts, and using data, and in Settings</string>
+    <!-- Title of dual sim onboarding's label sim dialog. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_label_sim_dialog_title">SIM label</string>
+    <!-- Label text of dual sim onboarding's label sim dialog. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_label_sim_dialog_label">Label</string>
+    <!-- Title of dual sim onboarding's select sim page. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_select_sim_title">Select which SIMs to use</string>
+    <!-- Body text of dual sim onboarding's select sim page. [CHAR LIMIT=NONE] -->
+    <string name="sim_onboarding_select_sim_msg">You can use 2 SIMs at a time</string>
+    <!-- Title of dual sim onboarding's primary sim page. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_primary_sim_title">Set your primary SIMs</string>
+    <!-- Body text of dual sim onboarding's primary sim page. [CHAR LIMIT=NONE] -->
+    <string name="sim_onboarding_primary_sim_msg">Choose which SIMs to use by default for calls, texts, and data</string>
+
+    <!-- Title of primary sim at SIMs page. [CHAR LIMIT=30] -->
+    <string name="primary_sim_title">Your primary SIMs</string>
+
+    <!-- Title of Calls item/dialog at dual sim onboarding's primary sim page or SIMs page. [CHAR LIMIT=30] -->
+    <string name="primary_sim_calls_title">Calls</string>
+    <!-- Title of Texts item/dialog at dual sim onboarding's primary sim page or SIMs page. [CHAR LIMIT=30] -->
+    <string name="primary_sim_texts_title">Texts</string>
+    <!-- Title of automatic data switching at dual sim onboarding's primary sim page or SIMs page. [CHAR LIMIT=30] -->
+    <string name="primary_sim_automatic_data_title">Automatic data switching</string>
+    <!-- Body text of automatic data switching at dual sim onboarding's primary sim page or SIMs page. [CHAR LIMIT=NONE] -->
+    <string name="primary_sim_automatic_data_msg">Use data from either SIM depending on coverage and availability</string>
+
+    <!-- Text of phone number item when the sim is data only. [CHAR LIMIT=NONE] -->
+    <string name="sim_onboarding_phoneNumber_data_only">Data only</string>
+
+    <!-- Button on the dual sim onboarding to start to set up sim. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_setup">Set up</string>
+    <!-- Button on the dual sim onboarding to go to next page. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_next">Next</string>
+    <!-- Text on the progressbar of dual sim onboarding for turning sim on. [CHAR LIMIT=30] -->
+    <string name="sim_onboarding_profressbar_turning_sim_on">Turning on <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>&#8230;</string>
+    <!-- Title of service provider name(SPN) at mobile network settings page. [CHAR LIMIT=30] -->
+    <string name="mobile_network_spn_title">Mobile network</string>
+    <!-- Title of phone number at mobile network settings page. [CHAR LIMIT=30] -->
+    <string name="mobile_network_phone_number_title">Phone number</string>
+    <!-- Title of SIM label and color editor dialog at mobile network settings page. [CHAR LIMIT=30] -->
+    <string name="mobile_network_sim_label_color_title">SIM label and color</string>
+
     <!--  Strings for SIM push notifications  -->
     <!-- Category name of the notifications related to SIM setup. [CHAR LIMIT=NONE] -->
     <string name="sim_setup_channel_id">Network activation</string>
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
index 2f56f77..3396b8b 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
@@ -37,22 +37,20 @@
 import com.android.settings.bluetooth.Utils;
 import com.android.settings.connecteddevice.DevicePreferenceCallback;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.VolumeControlProfile;
-import com.android.settingslib.utils.ThreadUtils;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
 public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePreferenceController
         implements DevicePreferenceCallback {
-    private static final boolean DEBUG = BluetoothUtils.D;
-
     private static final String TAG = "AudioSharingDeviceVolumeGroupController";
     private static final String KEY = "audio_sharing_device_volume_group";
 
@@ -63,8 +61,43 @@
     private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
     private FragmentManager mFragmentManager;
     private PreferenceGroup mPreferenceGroup;
-    private Map<Preference, BluetoothVolumeControl.Callback> mCallbackMap =
-            new HashMap<Preference, BluetoothVolumeControl.Callback>();
+    private List<AudioSharingDeviceVolumePreference> mVolumePreferences = new ArrayList<>();
+    private Map<Integer, Integer> mValueMap = new HashMap<Integer, Integer>();
+
+    private BluetoothVolumeControl.Callback mVolumeControlCallback =
+            new BluetoothVolumeControl.Callback() {
+                @Override
+                public void onVolumeOffsetChanged(
+                        @NonNull BluetoothDevice device, int volumeOffset) {}
+
+                @Override
+                public void onDeviceVolumeChanged(
+                        @NonNull BluetoothDevice device,
+                        @IntRange(from = -255, to = 255) int volume) {
+                    CachedBluetoothDevice cachedDevice =
+                            mLocalBtManager.getCachedDeviceManager().findDevice(device);
+                    if (cachedDevice == null) return;
+                    mValueMap.put(cachedDevice.getGroupId(), volume);
+                    for (AudioSharingDeviceVolumePreference preference : mVolumePreferences) {
+                        if (preference.getCachedDevice() != null
+                                && preference.getCachedDevice().getGroupId()
+                                        == cachedDevice.getGroupId()) {
+                            // If the callback return invalid volume, try to
+                            // get the volume from AudioManager.STREAM_MUSIC
+                            int finalVolume = getAudioVolumeIfNeeded(volume);
+                            Log.d(
+                                    TAG,
+                                    "onDeviceVolumeChanged: set volume to "
+                                            + finalVolume
+                                            + " for "
+                                            + device.getAnonymizedAddress());
+                            mContext.getMainExecutor()
+                                    .execute(() -> preference.setProgress(finalVolume));
+                            break;
+                        }
+                    }
+                }
+            };
 
     private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
             new BluetoothLeBroadcastAssistant.Callback() {
@@ -176,6 +209,10 @@
         }
         mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
         mBluetoothDeviceUpdater.registerCallback();
+        if (mVolumeControl != null) {
+            Log.d(TAG, "onStart() Registered volume control callback");
+            mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
+        }
     }
 
     @Override
@@ -191,17 +228,16 @@
         }
         mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
         mBluetoothDeviceUpdater.unregisterCallback();
+        if (mVolumeControl != null) {
+            Log.d(TAG, "onStop() Unregistered volume control callback");
+            mVolumeControl.unregisterCallback(mVolumeControlCallback);
+            mValueMap.clear();
+        }
     }
 
     @Override
     public void onDestroy(@NonNull LifecycleOwner owner) {
-        for (var entry : mCallbackMap.entrySet()) {
-            if (DEBUG) {
-                Log.d(TAG, "onDestroy: unregister callback for " + entry.getKey());
-            }
-            mVolumeControl.unregisterCallback(entry.getValue());
-        }
-        mCallbackMap.clear();
+        mVolumePreferences.clear();
     }
 
     @Override
@@ -228,14 +264,22 @@
             mPreferenceGroup.setVisible(true);
         }
         mPreferenceGroup.addPreference(preference);
-        if (mVolumeControl != null && preference instanceof AudioSharingDeviceVolumePreference) {
-            BluetoothVolumeControl.Callback callback =
-                    buildVcCallback((AudioSharingDeviceVolumePreference) preference);
-            mCallbackMap.put(preference, callback);
-            if (DEBUG) {
-                Log.d(TAG, "onDeviceAdded: register callback for " + preference);
-            }
-            mVolumeControl.registerCallback(mExecutor, callback);
+        if (preference instanceof AudioSharingDeviceVolumePreference) {
+            var volumePref = (AudioSharingDeviceVolumePreference) preference;
+            mVolumePreferences.add(volumePref);
+            if (volumePref.getProgress() > 0) return;
+            CachedBluetoothDevice device = volumePref.getCachedDevice();
+            if (device == null) return;
+            int volume = mValueMap.getOrDefault(device.getGroupId(), -1);
+            // If the volume is invalid, try to get the volume from AudioManager.STREAM_MUSIC
+            int finalVolume = getAudioVolumeIfNeeded(volume);
+            Log.d(
+                    TAG,
+                    "onDeviceAdded: set volume to "
+                            + finalVolume
+                            + " for "
+                            + device.getDevice().getAnonymizedAddress());
+            mContext.getMainExecutor().execute(() -> volumePref.setProgress(finalVolume));
         }
     }
 
@@ -245,12 +289,18 @@
         if (mPreferenceGroup.getPreferenceCount() == 0) {
             mPreferenceGroup.setVisible(false);
         }
-        if (mVolumeControl != null && mCallbackMap.containsKey(preference)) {
-            if (DEBUG) {
-                Log.d(TAG, "onDeviceRemoved: unregister callback for " + preference);
+        if (preference instanceof AudioSharingDeviceVolumePreference) {
+            var volumePref = (AudioSharingDeviceVolumePreference) preference;
+            if (mVolumePreferences.contains(volumePref)) {
+                mVolumePreferences.remove(volumePref);
             }
-            mVolumeControl.unregisterCallback(mCallbackMap.get(preference));
-            mCallbackMap.remove(preference);
+            CachedBluetoothDevice device = volumePref.getCachedDevice();
+            Log.d(
+                    TAG,
+                    "onDeviceRemoved: "
+                            + (device == null
+                                    ? "null"
+                                    : device.getDevice().getAnonymizedAddress()));
         }
     }
 
@@ -278,39 +328,6 @@
                         fragment.getMetricsCategory());
     }
 
-    private BluetoothVolumeControl.Callback buildVcCallback(
-            AudioSharingDeviceVolumePreference preference) {
-        return new BluetoothVolumeControl.Callback() {
-            @Override
-            public void onVolumeOffsetChanged(BluetoothDevice device, int volumeOffset) {}
-
-            @Override
-            public void onDeviceVolumeChanged(
-                    @NonNull BluetoothDevice device,
-                    @IntRange(from = -255, to = 255) int volume) {
-                CachedBluetoothDevice cachedDevice =
-                        mLocalBtManager.getCachedDeviceManager().findDevice(device);
-                if (cachedDevice == null) return;
-                if (preference.getCachedDevice() != null
-                        && preference.getCachedDevice().getGroupId() == cachedDevice.getGroupId()) {
-                    // If the callback return invalid volume, try to get the volume from
-                    // AudioManager.STREAM_MUSIC
-                    int finalVolume = getAudioVolumeIfNeeded(volume);
-                    Log.d(
-                            TAG,
-                            "onDeviceVolumeChanged: set volume to "
-                                    + finalVolume
-                                    + " for "
-                                    + device.getAnonymizedAddress());
-                    ThreadUtils.postOnMainThread(
-                            () -> {
-                                preference.setProgress(finalVolume);
-                            });
-                }
-            }
-        };
-    }
-
     private int getAudioVolumeIfNeeded(int volume) {
         if (volume >= 0) return volume;
         try {
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java
index 5d9202f..e259cad 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java
@@ -16,16 +16,10 @@
 
 package com.android.settings.testutils.shadow;
 
-import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
-
 import android.accounts.Account;
 import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.SyncAdapterType;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.provider.SearchIndexablesContract;
 import android.text.TextUtils;
 
 import org.robolectric.annotation.Implementation;
@@ -35,7 +29,7 @@
 import java.util.Map;
 
 @Implements(ContentResolver.class)
-public class ShadowContentResolver {
+public class ShadowContentResolver extends org.robolectric.shadows.ShadowContentResolver {
 
     private static SyncAdapterType[] sSyncAdapterTypes = new SyncAdapterType[0];
     private static Map<String, Integer> sSyncable = new HashMap<>();
@@ -48,23 +42,14 @@
     }
 
     @Implementation
-    protected final Cursor query(Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
-        MatrixCursor.RowBuilder builder = cursor.newRow()
-                .add(SearchIndexablesContract.NonIndexableKey.COLUMN_KEY_VALUE, "");
-        return cursor;
-    }
-
-    @Implementation
     protected static int getIsSyncableAsUser(Account account, String authority, int userId) {
-        return sSyncable.containsKey(authority) ? sSyncable.get(authority) : 1;
+        return sSyncable.getOrDefault(authority, 1);
     }
 
     @Implementation
     protected static boolean getSyncAutomaticallyAsUser(Account account, String authority,
             int userId) {
-        return sSyncAutomatically.containsKey(authority) ? sSyncAutomatically.get(authority) : true;
+        return sSyncAutomatically.getOrDefault(authority, true);
     }
 
     @Implementation