Merge "Check isSimHardwareVisible earlier" into main
diff --git a/res/layout/privatespace_advancing_screen.xml b/res/layout/privatespace_advancing_screen.xml
index cebb6fa..5b69593 100644
--- a/res/layout/privatespace_advancing_screen.xml
+++ b/res/layout/privatespace_advancing_screen.xml
@@ -20,7 +20,6 @@
     android:id="@+id/privatesapce_autoadvance_screen"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    app:sucHeaderText="@string/privatespace_lock_protected_title"
     android:icon="@drawable/ic_privatespace_icon">
     <LinearLayout style="@style/SudContentFrame"
                   android:layout_width="match_parent"
@@ -34,26 +33,14 @@
             android:contentDescription="@null"
             android:src="@drawable/privatespace_setup_flow_placeholder"/>
 
-        <LinearLayout
-            android:id="@+id/setup_progress"
+        <TextView
+            android:id="@+id/createMessage"
+            style="@style/PrivateSpaceSetupTextFontStyle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="bottom|right"
-            android:layout_margin="16dp"
-            android:orientation="horizontal">
-            <ProgressBar
-                android:id="@+id/progressBar_cyclic"
-                style="?android:attr/progressBarStyleSmall"
-                android:layout_width="20dp"
-                android:layout_height="20dp"
-                android:layout_gravity="center"/>
-            <TextView
-                android:id="@+id/createMessage"
-                style="@style/PrivateSpaceSetupTextFontStyle"
-                android:textSize="14sp"
-                android:text="@string/privatespace_setting_up_text"
-                android:layout_margin="8dp"/>
-        </LinearLayout>
+            android:textSize="14sp"
+            android:text="@string/privatespace_setting_up_text"
+            android:layout_marginBottom="24dp"/>
 
     </LinearLayout>
 </com.google.android.setupdesign.GlifLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e4e5750..1bd943c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1278,12 +1278,12 @@
     <string name="privatespace_apps_permission_text">Private Space apps won\u2019t appear in permission manager, privacy dashboard, and other settings when Private Space is locked</string>
     <!-- Text shown at the bottom in Private Space auto advancing  screens. [CHAR LIMIT=60] -->
     <string name="privatespace_setting_up_text">Setting up Private Space\u2026</string>
-    <!-- Title for Private Space setup in auto advancing screen informing private space is protected by a lock. [CHAR LIMIT=60] -->
-    <string name="privatespace_lock_protected_title">Private Space is protected by a lock</string>
     <!-- Title for Private Space setup in auto advancing screen informing private space is hidden when locked. [CHAR LIMIT=NONE] -->
     <string name="privatespace_apps_hidden_title">Usage info for Private Space apps is hidden when it\u2019s locked</string>
     <!-- Title for Private Space setup in auto advancing screen informing private space can be accessed from apps list. [CHAR LIMIT=60] -->
     <string name="privatespace_access_from_apps_title">Access Private Space from your apps list</string>
+    <!-- Title for Private Space setup in auto advancing screen informing some system apps are already installed in Private Space. [CHAR LIMIT=NONE] -->
+    <string name="privatespace_system_apps_installed_title">Some system apps are already installed in Private Space</string>
     <!-- Title for Private Space creation error screen. [CHAR LIMIT=60] -->
     <string name="privatespace_error_screen_title">Couldn\u2019t set up Private Space</string>
     <!-- Summary for the Private Space creation error screen. [CHAR LIMIT=60] -->
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java
index a68117a..5998e30 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java
@@ -22,10 +22,12 @@
 public final class AudioSharingDeviceItem implements Parcelable {
     private final String mName;
     private final int mGroupId;
+    private final boolean mIsActive;
 
-    public AudioSharingDeviceItem(String name, int groupId) {
+    public AudioSharingDeviceItem(String name, int groupId, boolean isActive) {
         mName = name;
         mGroupId = groupId;
+        mIsActive = isActive;
     }
 
     public String getName() {
@@ -36,15 +38,21 @@
         return mGroupId;
     }
 
+    public boolean isActive() {
+        return mIsActive;
+    }
+
     public AudioSharingDeviceItem(Parcel in) {
         mName = in.readString();
         mGroupId = in.readInt();
+        mIsActive = in.readBoolean();
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mName);
         dest.writeInt(mGroupId);
+        dest.writeBoolean(mIsActive);
     }
 
     @Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index 4ece70e..a0d44ff 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -120,6 +120,9 @@
     /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */
     public static AudioSharingDeviceItem buildAudioSharingDeviceItem(
             CachedBluetoothDevice cachedDevice) {
-        return new AudioSharingDeviceItem(cachedDevice.getName(), cachedDevice.getGroupId());
+        return new AudioSharingDeviceItem(
+                cachedDevice.getName(),
+                cachedDevice.getGroupId(),
+                BluetoothUtils.isActiveLeAudioDevice(cachedDevice));
     }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
index 0577f70..47f70c7 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
@@ -28,9 +28,25 @@
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
 import com.android.settings.flags.Flags;
 
+import java.util.ArrayList;
+
 /** Provides a dialog to choose the active device for calls and alarms. */
 public class CallsAndAlarmsDialogFragment extends InstrumentedDialogFragment {
     private static final String TAG = "CallsAndAlarmsDialog";
+    private static final String BUNDLE_KEY_DEVICE_ITEMS = "bundle_key_device_items";
+
+    // The host creates an instance of this dialog fragment must implement this interface to receive
+    // event callbacks.
+    public interface DialogEventListener {
+        /**
+         * Called when users click the device item to set active for calls and alarms in the dialog.
+         *
+         * @param item The device item clicked.
+         */
+        void onItemClick(AudioSharingDeviceItem item);
+    }
+
+    private static DialogEventListener sListener;
 
     @Override
     public int getMetricsCategory() {
@@ -41,28 +57,43 @@
      * Display the {@link CallsAndAlarmsDialogFragment} dialog.
      *
      * @param host The Fragment this dialog will be hosted.
+     * @param deviceItems The connected device items in audio sharing session.
+     * @param listener The callback to handle the user action on this dialog.
      */
-    public static void show(Fragment host) {
+    public static void show(
+            Fragment host,
+            ArrayList<AudioSharingDeviceItem> deviceItems,
+            DialogEventListener listener) {
         if (!Flags.enableLeAudioSharing()) return;
         final FragmentManager manager = host.getChildFragmentManager();
+        sListener = listener;
         if (manager.findFragmentByTag(TAG) == null) {
+            final Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS, deviceItems);
             final CallsAndAlarmsDialogFragment dialog = new CallsAndAlarmsDialogFragment();
+            dialog.setArguments(bundle);
             dialog.show(manager, TAG);
         }
     }
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // TODO: use real device names
-        String[] choices = {"Buds 1", "Buds 2"};
+        Bundle arguments = requireArguments();
+        ArrayList<AudioSharingDeviceItem> deviceItems =
+                arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS);
+        int checkedItem = -1;
+        // deviceItems is ordered. The active device is put in the first place if it does exist
+        if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) checkedItem = 0;
+        String[] choices =
+                deviceItems.stream().map(AudioSharingDeviceItem::getName).toArray(String[]::new);
         AlertDialog.Builder builder =
                 new AlertDialog.Builder(getActivity())
                         .setTitle(R.string.calls_and_alarms_device_title)
                         .setSingleChoiceItems(
                                 choices,
-                                0, // TODO: set to current active device.
+                                checkedItem,
                                 (dialog, which) -> {
-                                    // TODO: set device to active device for calls and alarms.
+                                    sListener.onItemClick(deviceItems.get(which));
                                 });
         return builder.create();
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
index 44e75ec..a7d18e7 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
@@ -16,23 +16,42 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.PreferenceScreen;
 
+import com.android.settings.bluetooth.Utils;
 import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /** PreferenceController to control the dialog to choose the active device for calls and alarms */
-public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController {
+public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController
+        implements BluetoothCallback, DefaultLifecycleObserver {
 
     private static final String TAG = "CallsAndAlarmsPreferenceController";
-
     private static final String PREF_KEY = "calls_and_alarms";
+
+    private final LocalBluetoothManager mLocalBtManager;
     private DashboardFragment mFragment;
+    Map<Integer, List<CachedBluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
+    private ArrayList<AudioSharingDeviceItem> mDeviceItemsInSharingSession = new ArrayList<>();
 
     public CallsAndAlarmsPreferenceController(Context context) {
         super(context, PREF_KEY);
+        mLocalBtManager = Utils.getLocalBtManager(mContext);
     }
 
     @Override
@@ -43,17 +62,60 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
+        updateDeviceItemsInSharingSession();
+        // mDeviceItemsInSharingSession is ordered. The active device is the first place if exits.
+        if (!mDeviceItemsInSharingSession.isEmpty()
+                && mDeviceItemsInSharingSession.get(0).isActive()) {
+            mPreference.setSummary(mDeviceItemsInSharingSession.get(0).getName());
+        } else {
+            mPreference.setSummary("");
+        }
         mPreference.setOnPreferenceClickListener(
                 preference -> {
-                    if (mFragment != null) {
-                        CallsAndAlarmsDialogFragment.show(mFragment);
-                    } else {
+                    if (mFragment == null) {
                         Log.w(TAG, "Dialog fail to show due to null host.");
+                        return true;
+                    }
+                    updateDeviceItemsInSharingSession();
+                    if (mDeviceItemsInSharingSession.size() >= 2) {
+                        CallsAndAlarmsDialogFragment.show(
+                                mFragment,
+                                mDeviceItemsInSharingSession,
+                                (AudioSharingDeviceItem item) -> {
+                                    for (CachedBluetoothDevice device :
+                                            mGroupedConnectedDevices.get(item.getGroupId())) {
+                                        device.setActive();
+                                    }
+                                });
                     }
                     return true;
                 });
     }
 
+    @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        if (mLocalBtManager != null) {
+            mLocalBtManager.getEventManager().registerCallback(this);
+        }
+    }
+
+    @Override
+    public void onStop(@NonNull LifecycleOwner owner) {
+        if (mLocalBtManager != null) {
+            mLocalBtManager.getEventManager().unregisterCallback(this);
+        }
+    }
+
+    @Override
+    public void onActiveDeviceChanged(
+            @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+        if (bluetoothProfile != BluetoothProfile.LE_AUDIO) {
+            Log.d(TAG, "Ignore onActiveDeviceChanged, not LE_AUDIO profile");
+            return;
+        }
+        mPreference.setSummary(activeDevice == null ? "" : activeDevice.getName());
+    }
+
     /**
      * Initialize the controller.
      *
@@ -62,4 +124,12 @@
     public void init(DashboardFragment fragment) {
         this.mFragment = fragment;
     }
+
+    private void updateDeviceItemsInSharingSession() {
+        mGroupedConnectedDevices =
+                AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
+        mDeviceItemsInSharingSession =
+                AudioSharingUtils.buildOrderedDeviceItemsInSharingSession(
+                        mGroupedConnectedDevices, mLocalBtManager);
+    }
 }
diff --git a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
index 3b59166..1061498 100644
--- a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
+++ b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
@@ -19,6 +19,11 @@
 import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION;
 import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.os.Bundle;
@@ -51,16 +56,17 @@
     private static final String TAG = "AutoAdvanceFragment";
     private static final String TITLE_INDEX = "title_index";
     private static final int DELAY_BETWEEN_SCREENS = 5000; // 5 seconds in millis
+    private static final int ANIMATION_DURATION_MILLIS = 500;
     private GlifLayout mRootView;
     private Handler mHandler;
     private int mScreenTitleIndex;
     private static final List<Pair<Integer, Integer>> HEADER_IMAGE_PAIRS =
             ImmutableList.of(
-                    new Pair(R.string.privatespace_lock_protected_title,
-                            R.drawable.privatespace_setup_flow_placeholder),
                     new Pair(R.string.privatespace_apps_hidden_title,
                             R.drawable.privatespace_setup_flow_placeholder),
                     new Pair(R.string.privatespace_access_from_apps_title,
+                            R.drawable.privatespace_setup_flow_placeholder),
+                    new Pair(R.string.privatespace_system_apps_installed_title,
                             R.drawable.privatespace_setup_flow_placeholder));
 
     private Runnable mUpdateScreenResources =
@@ -69,7 +75,7 @@
                 public void run() {
                     if (getActivity() != null) {
                         if (++mScreenTitleIndex < HEADER_IMAGE_PAIRS.size()) {
-                            updateHeaderAndImage();
+                            startFadeOutAnimation();
                             mHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS);
                         } else {
                             PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer
@@ -148,5 +154,32 @@
         mRootView.setHeaderText(HEADER_IMAGE_PAIRS.get(mScreenTitleIndex).first);
         ((ImageView) mRootView.findViewById(R.id.placeholder_image))
                 .setImageResource(HEADER_IMAGE_PAIRS.get(mScreenTitleIndex).second);
+        startFadeInAnimation();
+    }
+
+    private  void startFadeInAnimation() {
+        ValueAnimator textView =  ObjectAnimator.ofFloat(
+                mRootView.getHeaderTextView(), View.ALPHA, 0f, 1f);
+        ValueAnimator imageView = ObjectAnimator.ofFloat(
+                mRootView.findViewById(R.id.placeholder_image), View.ALPHA, 0, 1f);
+        AnimatorSet fadeIn = new AnimatorSet();
+        fadeIn.playTogether(textView, imageView);
+        fadeIn.setDuration(ANIMATION_DURATION_MILLIS).start();
+    }
+
+    private void startFadeOutAnimation() {
+        AnimatorSet fadeOut = new AnimatorSet();
+        ValueAnimator textView =  ObjectAnimator.ofFloat(
+                mRootView.getHeaderTextView(), View.ALPHA, 1f, 0f);
+        ValueAnimator imageView = ObjectAnimator.ofFloat(
+                mRootView.findViewById(R.id.placeholder_image), View.ALPHA, 1f, 0f);
+        fadeOut.playTogether(textView, imageView);
+        fadeOut.setDuration(ANIMATION_DURATION_MILLIS).start();
+        fadeOut.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                updateHeaderAndImage();
+            }
+        });
     }
 }