Merge "Narrow down the condition of wallpaper target for occluded keyguard" into 24D1-dev
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index db0ea54..e60e0b0 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -33,7 +33,6 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alwaysShow="true"
-        android:elevation="@dimen/resolver_elevation"
         android:paddingTop="24dp"
         android:paddingStart="@dimen/resolver_edge_margin"
         android:paddingEnd="@dimen/resolver_edge_margin"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 274183d..5ac99cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,7 +31,6 @@
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
-import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -538,15 +537,15 @@
         final int mode = change.getMode();
         // Put all the OPEN/SHOW on top
         if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
-            if (isOpening
-                    // This is for when an activity launches while a different transition is
-                    // collecting.
-                    || change.hasFlags(FLAG_MOVED_TO_TOP)) {
+            if (isOpening) {
                 // put on top
                 return zSplitLine + numChanges - i;
-            } else {
+            } else if (isClosing) {
                 // put on bottom
                 return zSplitLine - i;
+            } else {
+                // maintain relative ordering (put all changes in the animating layer)
+                return zSplitLine + numChanges - i;
             }
         } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
             if (isOpening) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
new file mode 100644
index 0000000..e17948f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 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.settingslib.satellite
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.telephony.satellite.SatelliteManager
+import android.util.Log
+import android.view.WindowManager
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.settingslib.wifi.WifiUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.Default
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
+import kotlin.coroutines.resume
+
+/** A util for Satellite dialog */
+object SatelliteDialogUtils {
+
+    /**
+     * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+     * Wifi during the satellite mode is on.
+     */
+    @JvmStatic
+    fun mayStartSatelliteWarningDialog(
+            context: Context,
+            lifecycleOwner: LifecycleOwner,
+            type: Int,
+            allowClick: (isAllowed: Boolean) -> Unit
+    ): Job {
+        return mayStartSatelliteWarningDialog(
+                context, lifecycleOwner.lifecycleScope, type, allowClick)
+    }
+
+    /**
+     * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+     * Wifi during the satellite mode is on.
+     */
+    @JvmStatic
+    fun mayStartSatelliteWarningDialog(
+            context: Context,
+            coroutineScope: CoroutineScope,
+            type: Int,
+            allowClick: (isAllowed: Boolean) -> Unit
+    ): Job =
+            coroutineScope.launch {
+                var isSatelliteModeOn = false
+                try {
+                    isSatelliteModeOn = requestIsEnabled(context)
+                } catch (e: InterruptedException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                } catch (e: ExecutionException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                } catch (e: TimeoutException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                }
+
+                if (isSatelliteModeOn) {
+                    startSatelliteWarningDialog(context, type)
+                }
+                withContext(Dispatchers.Main) {
+                    allowClick(!isSatelliteModeOn)
+                }
+            }
+
+    private fun startSatelliteWarningDialog(context: Context, type: Int) {
+        context.startActivity(Intent(Intent.ACTION_MAIN).apply {
+            component = ComponentName(
+                    "com.android.settings",
+                    "com.android.settings.network.SatelliteWarningDialogActivity"
+            )
+            putExtra(WifiUtils.DIALOG_WINDOW_TYPE,
+                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+            putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type)
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+        })
+    }
+
+    /**
+     * Checks if the satellite modem is enabled.
+     *
+     * @param executor The executor to run the asynchronous operation on
+     * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled,
+     *         `false` otherwise.
+     */
+    private suspend fun requestIsEnabled(
+            context: Context,
+    ): Boolean = withContext(Default) {
+        val satelliteManager: SatelliteManager? =
+                context.getSystemService(SatelliteManager::class.java)
+        if (satelliteManager == null) {
+            Log.w(TAG, "SatelliteManager is null")
+            return@withContext false
+        }
+
+        suspendCancellableCoroutine {continuation ->
+            satelliteManager?.requestIsEnabled(Default.asExecutor(),
+                    object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+                        override fun onResult(result: Boolean) {
+                            Log.i(TAG, "Satellite modem enabled status: $result")
+                            continuation.resume(result)
+                        }
+
+                        override fun onError(error: SatelliteManager.SatelliteException) {
+                            super.onError(error)
+                            Log.w(TAG, "Can't get satellite modem enabled status", error)
+                            continuation.resume(false)
+                        }
+                    })
+        }
+    }
+
+    const val TAG = "SatelliteDialogUtils"
+
+    const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
+            "extra_type_of_satellite_warning_dialog"
+    const val TYPE_IS_UNKNOWN = -1
+    const val TYPE_IS_WIFI = 0
+    const val TYPE_IS_BLUETOOTH = 1
+    const val TYPE_IS_AIRPLANE_MODE = 2
+}
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index f4ddd0a..916e571 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -41,7 +41,10 @@
 //###########################################################
 android_robolectric_test {
     name: "SettingsLibRoboTests",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     static_libs: [
         "Settings_robolectric_meta_service_file",
         "Robolectric_shadows_androidx_fragment_upstream",
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
new file mode 100644
index 0000000..aeda1ed6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 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.settingslib.satellite
+
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.util.AndroidRuntimeException
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.telephony.flags.Flags
+import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.verify
+import org.mockito.internal.verification.Times
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SatelliteDialogUtilsTest {
+    @JvmField
+    @Rule
+    val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    var context: Context = ApplicationProvider.getApplicationContext()
+    @Mock
+    private lateinit var satelliteManager: SatelliteManager
+
+    private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+    @Before
+    fun setUp() {
+        `when`(context.getSystemService(SatelliteManager::class.java))
+                .thenReturn(satelliteManager)
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onResult(true)
+                    null
+                }
+
+        try {
+            SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                        assertTrue(it)
+                })
+        } catch (e: AndroidRuntimeException) {
+            // Catch exception of starting activity .
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onResult(false)
+                    null
+                }
+
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
+        `when`(context.getSystemService(SatelliteManager::class.java))
+                .thenReturn(null)
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
+                    null
+                }
+
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+}
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
index bb8cece..ad6c154 100644
--- a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
@@ -14,10 +14,14 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape
+<inset
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:shape="rectangle">
-    <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
-    <corners android:radius="10000dp"/>  <!-- fully-rounded radius -->
-</shape>
+    android:insetLeft="@dimen/overlay_action_container_minimum_edge_spacing"
+    android:insetRight="@dimen/overlay_action_container_minimum_edge_spacing">
+    <shape
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:shape="rectangle">
+        <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+        <corners android:radius="10000dp"/>  <!-- fully-rounded radius -->
+    </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml
index 521369e..65005f8 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay2.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml
@@ -24,6 +24,16 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:contentDescription="@string/clipboard_overlay_window_name">
+    <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without
+         this we'd need to express margins as the sum of two different dimens). -->
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/min_edge_guideline"
+        app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
+        android:orientation="vertical"/>
+    <!-- Negative horizontal margin because this container background must render beyond the thing
+         it's constrained by (the actions themselves). -->
     <FrameLayout
         android:id="@+id/actions_container_background"
         android:visibility="gone"
@@ -31,11 +41,12 @@
         android:layout_width="0dp"
         android:elevation="4dp"
         android:background="@drawable/shelf_action_chip_container_background"
-        android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing"
+        android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing"
+        android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing"
         android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/actions_container"
-        app:layout_constraintEnd_toEndOf="@+id/actions_container"
+        app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
+        app:layout_constraintTop_toTopOf="@id/actions_container"
+        app:layout_constraintEnd_toEndOf="@id/actions_container"
         app:layout_constraintBottom_toBottomOf="parent"/>
     <HorizontalScrollView
         android:id="@+id/actions_container"
@@ -76,7 +87,7 @@
         android:layout_marginBottom="@dimen/overlay_preview_container_margin"
         android:elevation="7dp"
         android:background="@drawable/overlay_border"
-        app:layout_constraintStart_toStartOf="@id/actions_container_background"
+        app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
         app:layout_constraintTop_toTopOf="@id/clipboard_preview"
         app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
         app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 6b65e9c..796def3 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -79,7 +79,6 @@
             android:layout_width="wrap_content"
             android:elevation="4dp"
             android:background="@drawable/shelf_action_chip_container_background"
-            android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintBottom_toTopOf="@id/guideline"
             >
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c9abcaa..f418502 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -442,8 +442,12 @@
     <dimen name="overlay_preview_container_margin">8dp</dimen>
     <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
     <dimen name="overlay_action_container_margin_bottom">6dp</dimen>
-    <!-- minimum distance to the left, right or bottom edges. -->
+    <!--
+        minimum distance to the left, right or bottom edges. Keep in sync with
+        negative_overlay_action_container_minimum_edge_spacing. -->
     <dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen>
+    <!-- Keep in sync with overlay_action_container_minimum_edge_spacing -->
+    <dimen name="negative_overlay_action_container_minimum_edge_spacing">-12dp</dimen>
     <dimen name="overlay_bg_protection_height">242dp</dimen>
     <dimen name="overlay_action_container_corner_radius">20dp</dimen>
     <dimen name="overlay_action_container_padding_vertical">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 17251c3..f96bc040 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_AIRPLANE_MODE;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -33,9 +35,12 @@
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -56,6 +61,8 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.Job;
+
 /** Quick settings tile: Airplane mode **/
 public class AirplaneModeTile extends QSTileImpl<BooleanState> {
 
@@ -66,6 +73,9 @@
     private final Lazy<ConnectivityManager> mLazyConnectivityManager;
 
     private boolean mListening;
+    @Nullable
+    @VisibleForTesting
+    Job mClickJob;
 
     @Inject
     public AirplaneModeTile(
@@ -111,6 +121,21 @@
                     new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
             return;
         }
+
+        if (Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mContext, this, TYPE_IS_AIRPLANE_MODE, isAllowClick -> {
+                        if (isAllowClick) {
+                            setEnabled(!airplaneModeEnabled);
+                        }
+                        return null;
+                    });
+            return;
+        }
+
         setEnabled(!airplaneModeEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index b0707db..a351b07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH;
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
 import android.annotation.Nullable;
@@ -34,11 +35,14 @@
 import android.view.View;
 import android.widget.Switch;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -60,6 +64,8 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.Job;
+
 /** Quick settings tile: Bluetooth **/
 public class BluetoothTile extends QSTileImpl<BooleanState> {
 
@@ -78,6 +84,9 @@
     private final BluetoothTileDialogViewModel mDialogViewModel;
 
     private final FeatureFlags mFeatureFlags;
+    @Nullable
+    @VisibleForTesting
+    Job mClickJob;
 
     @Inject
     public BluetoothTile(
@@ -110,6 +119,24 @@
 
     @Override
     protected void handleClick(@Nullable View view) {
+        if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mContext, this, TYPE_IS_BLUETOOTH, isAllowClick -> {
+                        if (!isAllowClick) {
+                            return null;
+                        }
+                        handleClickEvent(view);
+                        return null;
+                    });
+            return;
+        }
+        handleClickEvent(view);
+    }
+
+    private void handleClickEvent(@Nullable View view) {
         if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
             mDialogViewModel.showDialog(view);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index dd40b7f..1d14a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.qs.tiles.dialog;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI;
 import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
 
@@ -57,6 +58,8 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
@@ -68,13 +71,16 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wifitrackerlib.WifiEntry;
 
-import java.util.List;
-import java.util.concurrent.Executor;
-
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
+
 /**
  * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
  */
@@ -159,13 +165,17 @@
     // Wi-Fi scanning progress bar
     protected boolean mIsProgressBarVisible;
     private SystemUIDialog mDialog;
+    private final CoroutineScope mCoroutineScope;
+    @Nullable
+    private Job mClickJob;
 
     @AssistedFactory
     public interface Factory {
         InternetDialogDelegate create(
                 @Assisted(ABOVE_STATUS_BAR) boolean aboveStatusBar,
                 @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigMobileData,
-                @Assisted(CAN_CONFIG_WIFI) boolean canConfigWifi);
+                @Assisted(CAN_CONFIG_WIFI) boolean canConfigWifi,
+                @Assisted CoroutineScope coroutineScope);
     }
 
     @AssistedInject
@@ -176,6 +186,7 @@
             @Assisted(ABOVE_STATUS_BAR) boolean canConfigMobileData,
             @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigWifi,
             @Assisted(CAN_CONFIG_WIFI) boolean aboveStatusBar,
+            @Assisted CoroutineScope coroutineScope,
             UiEventLogger uiEventLogger,
             DialogTransitionAnimator dialogTransitionAnimator,
             @Main Handler handler,
@@ -199,7 +210,7 @@
         mCanConfigWifi = canConfigWifi;
         mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
         mKeyguard = keyguardStateController;
-
+        mCoroutineScope = coroutineScope;
         mUiEventLogger = uiEventLogger;
         mDialogTransitionAnimator = dialogTransitionAnimator;
         mAdapter = new InternetAdapter(mInternetDialogController);
@@ -384,11 +395,9 @@
         });
         mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
         mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
-        mWiFiToggle.setOnCheckedChangeListener(
-                (buttonView, isChecked) -> {
-                    if (mInternetDialogController.isWifiEnabled() == isChecked) return;
-                    mInternetDialogController.setWifiEnabled(isChecked);
-                });
+        mWiFiToggle.setOnClickListener(v -> {
+            handleWifiToggleClicked(mWiFiToggle.isChecked());
+        });
         mDoneButton.setOnClickListener(v -> dialog.dismiss());
         mShareWifiButton.setOnClickListener(v -> {
             if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry)) {
@@ -400,6 +409,32 @@
         });
     }
 
+    private void handleWifiToggleClicked(boolean isChecked) {
+        if (Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mDialog.getContext(), mCoroutineScope, TYPE_IS_WIFI, isAllowClick -> {
+                        if (isAllowClick) {
+                            setWifiEnable(isChecked);
+                        } else {
+                            mWiFiToggle.setChecked(!isChecked);
+                        }
+                        return null;
+                    });
+            return;
+        }
+        setWifiEnable(isChecked);
+    }
+
+    private void setWifiEnable(boolean isChecked) {
+        if (mInternetDialogController.isWifiEnabled() == isChecked) {
+            return;
+        }
+        mInternetDialogController.setWifiEnabled(isChecked);
+    }
+
     @MainThread
     private void updateEthernet() {
         mEthernetLayout.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
index 2a177c7..5aef950 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
@@ -21,26 +21,35 @@
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
 
 private const val TAG = "InternetDialogFactory"
 private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
 
-/**
- * Factory to create [InternetDialogDelegate] objects.
- */
+/** Factory to create [InternetDialogDelegate] objects. */
 @SysUISingleton
-class InternetDialogManager @Inject constructor(
+class InternetDialogManager
+@Inject
+constructor(
     private val dialogTransitionAnimator: DialogTransitionAnimator,
-    private val dialogFactory: InternetDialogDelegate.Factory
+    private val dialogFactory: InternetDialogDelegate.Factory,
+    @Background private val bgDispatcher: CoroutineDispatcher,
 ) {
+    private lateinit var coroutineScope: CoroutineScope
     companion object {
         private const val INTERACTION_JANK_TAG = "internet"
         var dialog: SystemUIDialog? = null
     }
 
-    /** Creates a [InternetDialogDelegate]. The dialog will be animated from [view] if it is not null. */
+    /**
+     * Creates a [InternetDialogDelegate]. The dialog will be animated from [view] if it is not
+     * null.
+     */
     fun create(
         aboveStatusBar: Boolean,
         canConfigMobileData: Boolean,
@@ -53,16 +62,21 @@
             }
             return
         } else {
-            dialog = dialogFactory.create(
-                    aboveStatusBar, canConfigMobileData, canConfigWifi).createDialog()
+            coroutineScope = CoroutineScope(bgDispatcher)
+            dialog =
+                dialogFactory
+                    .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope)
+                    .createDialog()
             if (view != null) {
                 dialogTransitionAnimator.showFromView(
-                        dialog!!, view,
+                    dialog!!,
+                    view,
                     animateBackgroundBoundsChange = true,
-                    cuj = DialogCuj(
-                        InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                        INTERACTION_JANK_TAG
-                    )
+                    cuj =
+                        DialogCuj(
+                            InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                            INTERACTION_JANK_TAG
+                        )
                 )
             } else {
                 dialog!!.show()
@@ -74,6 +88,9 @@
         if (DEBUG) {
             Log.d(TAG, "destroyDialog")
         }
+        if (dialog != null) {
+            coroutineScope.cancel()
+        }
         dialog = null
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index e2a3fac6..26eae92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
+import com.android.internal.telephony.flags.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,10 +33,12 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.settings.GlobalSettings
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
+import kotlinx.coroutines.Job
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -44,11 +46,15 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class AirplaneModeTileTest : SysuiTestCase() {
+
     @Mock
     private lateinit var mHost: QSHost
     @Mock
@@ -62,13 +68,18 @@
     @Mock
     private lateinit var mBroadcastDispatcher: BroadcastDispatcher
     @Mock
-    private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
+    private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+    @Mock
+    private lateinit var mConnectivityManager: ConnectivityManager
     @Mock
     private lateinit var mGlobalSettings: GlobalSettings
     @Mock
     private lateinit var mUserTracker: UserTracker
     @Mock
     private lateinit var mUiEventLogger: QsEventLogger
+    @Mock
+    private lateinit var mClickJob: Job
+
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
@@ -78,7 +89,7 @@
         mTestableLooper = TestableLooper.get(this)
         Mockito.`when`(mHost.context).thenReturn(mContext)
         Mockito.`when`(mHost.userContext).thenReturn(mContext)
-
+        Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
         mTile = AirplaneModeTile(
             mHost,
             mUiEventLogger,
@@ -90,7 +101,7 @@
             mActivityStarter,
             mQsLogger,
             mBroadcastDispatcher,
-            mConnectivityManager,
+            mLazyConnectivityManager,
             mGlobalSettings,
             mUserTracker)
     }
@@ -120,4 +131,24 @@
         assertThat(state.icon)
             .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
     }
+
+    @Test
+    fun handleClick_noSatelliteFeature_directSetAirplaneMode() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+        mTile.handleClick(null)
+
+        verify(mConnectivityManager).setAirplaneMode(any())
+    }
+
+    @Test
+    fun handleClick_hasSatelliteFeatureButClickIsProcessing_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        Mockito.`when`(mClickJob.isCompleted).thenReturn(false)
+        mTile.mClickJob = mClickJob
+
+        mTile.handleClick(null)
+
+        verify(mConnectivityManager, times(0)).setAirplaneMode(any())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 82ee99a..bea6bc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,9 +9,9 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.telephony.flags.Flags
 import com.android.settingslib.Utils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -24,12 +24,14 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.BluetoothController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Job
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -37,6 +39,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -54,7 +57,7 @@
     @Mock private lateinit var uiEventLogger: QsEventLogger
     @Mock private lateinit var featureFlags: FeatureFlagsClassic
     @Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
-
+    @Mock private lateinit var clickJob: Job
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FakeBluetoothTile
 
@@ -191,6 +194,41 @@
     }
 
     @Test
+    fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(false)
+        `when`(clickJob.isCompleted).thenReturn(false)
+        tile.mClickJob = clickJob
+
+        tile.handleClick(null)
+
+        verify(bluetoothController, times(0)).setBluetoothEnabled(any())
+    }
+
+    @Test
+    fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(false)
+
+        tile.handleClick(null)
+
+        verify(bluetoothController).setBluetoothEnabled(any())
+    }
+
+    @Test
+    fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(true)
+
+        tile.handleClick(null)
+
+        verify(bluetoothTileDialogViewModel).showDialog(null)
+    }
+
+    @Test
     fun testMetadataListener_whenDisconnected_isUnregistered() {
         val state = QSTile.BooleanState()
         val cachedDevice = mock<CachedBluetoothDevice>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index f3351a2..56e0c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -1,7 +1,9 @@
 package com.android.systemui.qs.tiles.dialog;
 
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -50,6 +52,8 @@
 
 import java.util.List;
 
+import kotlinx.coroutines.CoroutineScope;
+
 @Ignore("b/257089187")
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -64,6 +68,8 @@
     @Mock
     private Handler mHandler;
     @Mock
+    CoroutineScope mScope;
+    @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private WifiEntry mInternetWifiEntry;
@@ -133,6 +139,7 @@
                 true,
                 true,
                 true,
+                mScope,
                 mock(UiEventLogger.class),
                 mDialogTransitionAnimator,
                 mHandler,
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 7116b0a..24d4be8 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -653,7 +653,7 @@
             // by drawing the rotated content before applying projection transaction of display.
             // And it will fade in after the display transition is finished.
             if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted
-                    && canBeAsync(w.mToken)) {
+                    && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()) {
                 hideImmediately(w.mToken, Operation.ACTION_FADE);
                 if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild());
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3bb8980..2cadc24 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1958,6 +1958,12 @@
                 && mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents;
     }
 
+    /** It usually means whether the recents activity is launching with a different rotation. */
+    boolean hasFixedRotationTransientLaunch() {
+        return mFixedRotationLaunchingApp != null
+                && mTransitionController.isTransientLaunch(mFixedRotationLaunchingApp);
+    }
+
     boolean isFixedRotationLaunchingApp(ActivityRecord r) {
         return mFixedRotationLaunchingApp == r;
     }
@@ -7006,9 +7012,8 @@
         boolean shouldDeferRotation() {
             ActivityRecord source = null;
             if (mTransitionController.isShellTransitionsEnabled()) {
-                final ActivityRecord r = mFixedRotationLaunchingApp;
-                if (r != null && mTransitionController.isTransientLaunch(r)) {
-                    source = r;
+                if (hasFixedRotationTransientLaunch()) {
+                    source = mFixedRotationLaunchingApp;
                 }
             } else if (mAnimatingRecents != null && !hasTopFixedRotationLaunchingApp()) {
                 source = mAnimatingRecents;