Merge "Remove usages of isDisplayOccluded" into main
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index fa2d677..1d25ac7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -106,5 +106,10 @@
Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV,
Settings.Global.Wearable.REDUCE_MOTION,
Settings.Global.Wearable.WEAR_LAUNCHER_UI_MODE,
+ Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
+ Settings.Global.Wearable.RSB_WAKE_ENABLED,
+ Settings.Global.Wearable.SCREENSHOT_ENABLED,
+ Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
+ Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
};
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 203efbf..92f65d6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -658,7 +658,6 @@
Settings.Global.Wearable.COMPANION_BLE_ROLE,
Settings.Global.Wearable.COMPANION_NAME,
Settings.Global.Wearable.COMPANION_APP_NAME,
- Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
Settings.Global.Wearable.COMPANION_OS_VERSION,
Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
Settings.Global.Wearable.SETUP_LOCALE,
@@ -673,16 +672,12 @@
Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
Settings.Global.Wearable.WET_MODE_ON,
Settings.Global.Wearable.COOLDOWN_MODE_ON,
- Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
- Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
- Settings.Global.Wearable.RSB_WAKE_ENABLED,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
- Settings.Global.Wearable.SCREENSHOT_ENABLED,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6778d5a..b5b873c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -349,6 +349,9 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
+ <!-- Listen to (dis-)connection of external displays and enable / disable them. -->
+ <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
new file mode 100644
index 0000000..569dd4c
--- /dev/null
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -0,0 +1,69 @@
+<!--
+ Copyright (C) 2023 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/dialog_side_padding"
+ android:paddingTop="@dimen/dialog_top_padding"
+ android:background="@*android:drawable/bottomsheet_background"
+ android:paddingBottom="@dimen/dialog_bottom_padding">
+
+ <ImageView
+ android:id="@+id/connected_display_dialog_icon"
+ android:layout_width="@dimen/screenrecord_logo_size"
+ android:layout_height="@dimen/screenrecord_logo_size"
+ android:importantForAccessibility="no"
+ android:src="@drawable/stat_sys_connected_display"
+ android:tint="?androidprv:attr/materialColorPrimary" />
+
+ <TextView
+ android:id="@+id/connected_display_dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+ android:gravity="center"
+ android:text="@string/connected_display_dialog_start_mirroring"
+ android:textAppearance="@style/TextAppearance.Dialog.Title" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/cancel"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/cancel" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/enable_display"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/enable_display" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b37aeee..6840108 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3184,6 +3184,12 @@
<!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
<string name="install_app">Install app</string>
+ <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
+ <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
+
+ <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+ <string name="enable_display">Enable display</string>
+
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone & Camera</string>
<!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index d3174f1..adb0bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -71,14 +71,9 @@
private var resolved: Boolean = false
@WorkerThread
- fun resolvePanelActivity(
- allowAllApps: Boolean = false
- ) {
+ fun resolvePanelActivity() {
if (resolved) return
resolved = true
- val validPackages = context.resources
- .getStringArray(R.array.config_controlsPreferredPackages)
- if (componentName.packageName !in validPackages && !allowAllApps) return
panelActivity = _panelActivity?.let {
val resolveInfos = mPm.queryIntentActivitiesAsUser(
Intent().setComponent(it),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 83bec66..74e1dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -33,7 +33,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.asIndenting
@@ -125,9 +124,8 @@
private fun updateServices(newServices: List<ControlsServiceInfo>) {
if (activityTaskManagerProxy.supportsMultiWindow(context)) {
- val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
newServices.forEach {
- it.resolvePanelActivity(allowAllApps) }
+ it.resolvePanelActivity() }
}
if (newServices != availableServices) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 6fdb4ca..dcacd09 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -22,6 +22,7 @@
import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.dagger.qualifiers.PerUser;
+import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
@@ -140,6 +141,7 @@
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
getUnfoldLatencyTracker().init();
+ getConnectingDisplayViewModel().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
@@ -229,6 +231,11 @@
NearbyMediaDevicesManager getNearbyMediaDevicesManager();
/**
+ * Creates a ConnectingDisplayViewModel
+ */
+ ConnectingDisplayViewModel getConnectingDisplayViewModel();
+
+ /**
* Returns {@link CoreStartable}s that should be started with the application.
*/
Map<Class<?>, Provider<CoreStartable>> getStartables();
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index b18f7bf..0c8e293 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -22,6 +22,7 @@
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
import android.os.Handler
+import android.util.Log
import android.view.Display
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -41,6 +42,13 @@
interface DisplayRepository {
/** Provides a nullable set of displays. */
val displays: Flow<Set<Display>>
+
+ /**
+ * Pending display id that can be enabled/disabled.
+ *
+ * When `null`, it means there is no pending display waiting to be enabled.
+ */
+ val pendingDisplay: Flow<Int?>
}
@SysUISingleton
@@ -85,8 +93,59 @@
initialValue = getDisplays()
)
- fun getDisplays(): Set<Display> =
+ private fun getDisplays(): Set<Display> =
traceSection("DisplayRepository#getDisplays()") {
displayManager.displays?.toSet() ?: emptySet()
}
+
+ override val pendingDisplay: Flow<Int?> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DisplayConnectionListener {
+ private val pendingIds = mutableSetOf<Int>()
+ override fun onDisplayConnected(id: Int) {
+ pendingIds += id
+ trySend(id)
+ }
+
+ override fun onDisplayDisconnected(id: Int) {
+ if (id in pendingIds) {
+ pendingIds -= id
+ trySend(null)
+ } else {
+ Log.e(
+ TAG,
+ "onDisplayDisconnected received for unknown display. " +
+ "id=$id, knownIds=$pendingIds"
+ )
+ }
+ }
+ }
+ displayManager.registerDisplayListener(
+ callback,
+ backgroundHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+ )
+ awaitClose { displayManager.unregisterDisplayListener(callback) }
+ }
+ .flowOn(backgroundCoroutineDispatcher)
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
+
+ private companion object {
+ const val TAG = "DisplayRepository"
+ }
+}
+
+/** Used to provide default implementations for all methods. */
+private interface DisplayConnectionListener : DisplayListener {
+
+ override fun onDisplayConnected(id: Int) {}
+ override fun onDisplayDisconnected(id: Int) {}
+ override fun onDisplayAdded(id: Int) {}
+ override fun onDisplayRemoved(id: Int) {}
+ override fun onDisplayChanged(id: Int) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 4b957c7..308b7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -16,10 +16,13 @@
package com.android.systemui.display.domain.interactor
+import android.hardware.display.DisplayManager
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.traceSection
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -37,18 +40,31 @@
*/
val connectedDisplayState: Flow<State>
+ /** Pending display that can be enabled to be used by the system. */
+ val pendingDisplay: Flow<PendingDisplay?>
+
/** Possible connected display state. */
enum class State {
DISCONNECTED,
CONNECTED,
CONNECTED_SECURE,
}
+
+ /** Represents a connected display that has not been enabled yet. */
+ interface PendingDisplay {
+ /** Enables the display, making it available to the system. */
+ fun enable()
+
+ /** Disables the display, making it unavailable to the system. */
+ fun disable()
+ }
}
@SysUISingleton
class ConnectedDisplayInteractorImpl
@Inject
constructor(
+ private val displayManager: DisplayManager,
displayRepository: DisplayRepository,
) : ConnectedDisplayInteractor {
@@ -70,4 +86,22 @@
}
}
.distinctUntilChanged()
+
+ override val pendingDisplay: Flow<PendingDisplay?> =
+ displayRepository.pendingDisplay.distinctUntilChanged().map { it?.toPendingDisplay() }
+
+ private fun Int.toPendingDisplay() =
+ object : PendingDisplay {
+ val id = this@toPendingDisplay
+ override fun enable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.enableConnectedDisplay(id)
+ }
+ }
+ override fun disable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.disableConnectedDisplay(id)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
new file mode 100644
index 0000000..174c6ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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.systemui.display.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import android.widget.TextView
+import com.android.systemui.R
+
+/** Dialog used to decide what to do with a connected display. */
+class MirroringConfirmationDialog(
+ context: Context,
+ private val onStartMirroringClickListener: View.OnClickListener,
+ private val onDismissClickListener: View.OnClickListener,
+) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
+
+ private lateinit var mirrorButton: TextView
+ private lateinit var dismissButton: TextView
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ setGravity(Gravity.BOTTOM)
+ }
+ setContentView(R.layout.connected_display_dialog)
+ setCanceledOnTouchOutside(true)
+ mirrorButton =
+ requireViewById<TextView>(R.id.enable_display).apply {
+ setOnClickListener(onStartMirroringClickListener)
+ }
+ dismissButton =
+ requireViewById<TextView>(R.id.cancel).apply {
+ setOnClickListener(onDismissClickListener)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
new file mode 100644
index 0000000..ece33b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.systemui.display.ui.viewmodel
+
+import android.app.Dialog
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
+import com.android.systemui.display.ui.view.MirroringConfirmationDialog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Shows/hides a dialog to allow the user to decide whether to use the external display for
+ * mirroring.
+ */
+@SysUISingleton
+class ConnectingDisplayViewModel
+@Inject
+constructor(
+ private val context: Context,
+ private val connectedDisplayInteractor: ConnectedDisplayInteractor,
+ @Application private val scope: CoroutineScope,
+) {
+
+ private var dialog: Dialog? = null
+
+ /** Starts listening for pending displays. */
+ fun init() {
+ connectedDisplayInteractor.pendingDisplay
+ .onEach { pendingDisplay ->
+ if (pendingDisplay == null) {
+ hideDialog()
+ } else {
+ showDialog(pendingDisplay)
+ }
+ }
+ .launchIn(scope)
+ }
+
+ private fun showDialog(pendingDisplay: PendingDisplay) {
+ hideDialog()
+ dialog =
+ MirroringConfirmationDialog(
+ context,
+ onStartMirroringClickListener = {
+ pendingDisplay.enable()
+ hideDialog()
+ },
+ onDismissClickListener = { hideDialog() }
+ )
+ .apply { show() }
+ }
+
+ private fun hideDialog() {
+ dialog?.hide()
+ dialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6b578ba..dfbaf55 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -634,9 +634,6 @@
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
- // 2000 - device controls
- @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed")
-
// 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
// TODO(b/259264861): Tracking Bug
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index b1061ba..74d0d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.concurrency.FakeExecutor
@@ -123,9 +122,6 @@
arrayOf(componentName.packageName)
)
- // Return false by default, we'll test the true path
- `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false)
-
val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
return baseContext
@@ -469,38 +465,7 @@
}
@Test
- fun testPackageNotPreferred_nullPanel() {
- mContext.orCreateTestableResources
- .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
-
- val serviceInfo = ServiceInfo(
- componentName,
- activityName
- )
-
- `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
- .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
-
- setUpQueryResult(listOf(
- ActivityInfo(
- activityName,
- exported = true,
- permission = Manifest.permission.BIND_CONTROLS
- )
- ))
-
- val list = listOf(serviceInfo)
- serviceListingCallbackCaptor.value.onServicesReloaded(list)
-
- executor.runAllReady()
-
- assertNull(controller.getCurrentServices()[0].panelActivity)
- }
-
- @Test
- fun testPackageNotPreferred_allowAllApps_correctPanel() {
- `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(true)
-
+ fun testPackageNotPreferred_correctPanel() {
mContext.orCreateTestableResources
.addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 9be54fb..7d836a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -165,13 +165,78 @@
assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
}
+ @Test
+ fun onDisplayConnected_pendingDisplayReceived() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isEqualTo(1)
+ }
+
+ @Test
+ fun onDisplayDisconnected_pendingDisplayNull() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ displayListener.value.onDisplayDisconnected(1)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ displayListener.value.onDisplayDisconnected(2)
+
+ assertThat(pendingDisplay).isNotNull()
+ }
+
+ @Test
+ fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+ displayListener.value.onDisplayConnected(2)
+
+ assertThat(pendingDisplay).isEqualTo(2)
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
// Wrapper to capture the displayListener.
private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
val flowValue = collectLastValue(displayRepository.displays)
verify(displayManager)
- .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+ .registerDisplayListener(
+ displayListener.capture(),
+ eq(testHandler),
+ eq(
+ DisplayManager.EVENT_FLAG_DISPLAY_ADDED or
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or
+ DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
+ )
+ )
+ return flowValue
+ }
+
+ private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
+ val flowValue = collectLastValue(displayRepository.pendingDisplay)
+ verify(displayManager)
+ .registerDisplayListener(
+ displayListener.capture(),
+ eq(testHandler),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+ )
return flowValue
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index eb0ad69..fb19eca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.display.domain.interactor
+import android.hardware.display.DisplayManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -27,7 +28,10 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -35,6 +39,7 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -42,9 +47,10 @@
@SmallTest
class ConnectedDisplayInteractorTest : SysuiTestCase() {
+ private val displayManager = mock<DisplayManager>()
private val fakeDisplayRepository = FakeDisplayRepository()
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
- ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+ ConnectedDisplayInteractorImpl(displayManager, fakeDisplayRepository)
private val testScope = TestScope(UnconfinedTestDispatcher())
@Test
@@ -126,6 +132,42 @@
assertThat(value).isEqualTo(State.CONNECTED_SECURE)
}
+ @Test
+ fun pendingDisplay_propagated() =
+ testScope.runTest {
+ val value by lastPendingDisplay()
+ val pendingDisplayId = 4
+
+ fakeDisplayRepository.emit(pendingDisplayId)
+
+ assertThat(value).isNotNull()
+ }
+
+ @Test
+ fun onPendingDisplay_enable_displayEnabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ pendingDisplay!!.enable()
+
+ Mockito.verify(displayManager).enableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_disable_displayDisabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ pendingDisplay!!.disable()
+
+ Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
+ }
+
private fun TestScope.lastValue(): FlowValue<State?> =
collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+ private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> =
+ collectLastValue(connectedDisplayStateProvider.pendingDisplay)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
new file mode 100644
index 0000000..7059647
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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.systemui.display.ui.view
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MirroringConfirmationDialogTest : SysuiTestCase() {
+
+ private lateinit var dialog: MirroringConfirmationDialog
+
+ private val onStartMirroringCallback = mock<View.OnClickListener>()
+ private val onCancelCallback = mock<View.OnClickListener>()
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ dialog = MirroringConfirmationDialog(context, onStartMirroringCallback, onCancelCallback)
+ }
+
+ @Test
+ fun startMirroringButton_clicked_callsCorrectCallback() {
+ dialog.show()
+
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ verify(onStartMirroringCallback).onClick(any())
+ verify(onCancelCallback, never()).onClick(any())
+ }
+
+ @Test
+ fun cancelButton_clicked_callsCorrectCallback() {
+ dialog.show()
+
+ dialog.requireViewById<View>(R.id.cancel).callOnClick()
+
+ verify(onCancelCallback).onClick(any())
+ verify(onStartMirroringCallback, never()).onClick(any())
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
index 786856b..66d2465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.privacy.PrivacyItemController
@@ -105,5 +106,7 @@
suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value)
override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State>
get() = flow
+ override val pendingDisplay: Flow<PendingDisplay?>
+ get() = MutableSharedFlow<PendingDisplay>()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 9795b9d..71c27de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
@@ -316,5 +317,7 @@
suspend fun emit(value: State) = flow.emit(value)
override val connectedDisplayState: Flow<State>
get() = flow
+ override val pendingDisplay: Flow<PendingDisplay?>
+ get() = TODO("Not yet implemented")
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 715d661..d54ef73 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -33,10 +33,17 @@
/** Fake [DisplayRepository] implementation for testing. */
class FakeDisplayRepository : DisplayRepository {
private val flow = MutableSharedFlow<Set<Display>>()
+ private val pendingDisplayFlow = MutableSharedFlow<Int?>()
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
+ /** Emits [value] as [pendingDisplay] flow value. */
+ suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
+
override val displays: Flow<Set<Display>>
get() = flow
+
+ override val pendingDisplay: Flow<Int?>
+ get() = pendingDisplayFlow
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index c3db241..4165911 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -26,7 +26,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -35,9 +34,14 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static java.util.Objects.requireNonNull;
+
import android.app.ActivityThread;
import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
@@ -50,6 +54,8 @@
import android.window.WindowContextInfo;
import android.window.WindowTokenClient;
+import androidx.annotation.NonNull;
+
import com.android.server.inputmethod.InputMethodDialogWindowContext;
import org.junit.After;
@@ -58,6 +64,9 @@
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
// TODO(b/157888351): Move the test to inputmethod package once we find the way to test the
// scenario there.
/**
@@ -138,44 +147,96 @@
@Test
public void testGetSettingsContextOnDualDisplayContent() {
final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
+ final MaxBoundsVerifier maxBoundsVerifier = new MaxBoundsVerifier();
+ context.registerComponentCallbacks(maxBoundsVerifier);
+
final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
- assertNotNull(tokenClient);
- spyOn(tokenClient);
+ spyOn(requireNonNull(tokenClient));
final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer();
spyOn(imeContainer);
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay);
- mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
+ final DisplayAreaGroup firstDaGroup = mSecondaryDisplay.mFirstRoot;
+ maxBoundsVerifier.setMaxBounds(firstDaGroup.getMaxBounds());
+
+ firstDaGroup.placeImeContainer(imeContainer);
verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
+ eq(firstDaGroup.getConfiguration()));
verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
+ eq(firstDaGroup.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
- assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
+ assertThat(imeContainer.getRootDisplayArea()).isEqualTo(firstDaGroup);
+ maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
// Clear the previous invocation histories in case we may count the previous
// onConfigurationChanged invocation into the next verification.
clearInvocations(tokenClient, imeContainer);
- mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
+ final DisplayAreaGroup secondDaGroup = mSecondaryDisplay.mSecondRoot;
+ maxBoundsVerifier.setMaxBounds(secondDaGroup.getMaxBounds());
+
+ secondDaGroup.placeImeContainer(imeContainer);
verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
+ eq(secondDaGroup.getConfiguration()));
verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
+ eq(secondDaGroup.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
- assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
+ assertThat(imeContainer.getRootDisplayArea()).isEqualTo(secondDaGroup);
+ maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
}
private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {
assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId());
+ final Rect imeContainerBounds = dc.getImeContainer().getBounds();
final Rect contextBounds = context.getSystemService(WindowManager.class)
.getMaximumWindowMetrics().getBounds();
- final Rect imeContainerBounds = dc.getImeContainer().getBounds();
assertThat(contextBounds).isEqualTo(imeContainerBounds);
}
+
+ private static final class MaxBoundsVerifier implements ComponentCallbacks {
+
+ private CountDownLatch mLatch;
+
+ private Rect mMaxBounds;
+
+ /**
+ * Sets max bounds to verify whether it matches the
+ * {@link WindowConfiguration#getMaxBounds()} reported from
+ * {@link #onConfigurationChanged(Configuration)} callback, and also resets the count down
+ * latch.
+ *
+ * @param maxBounds max bounds to verify
+ */
+ private void setMaxBounds(@NonNull Rect maxBounds) {
+ mMaxBounds = maxBounds;
+ mLatch = new CountDownLatch(1);
+ }
+
+ /**
+ * Waits for the {@link #onConfigurationChanged(Configuration)} callback whose the reported
+ * {@link WindowConfiguration#getMaxBounds()} matches {@link #mMaxBounds}.
+ */
+ private void waitAndAssertMaxMetricsMatches() {
+ try {
+ assertThat(mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Test failed because of " + e);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ if (newConfig.windowConfiguration.getMaxBounds().equals(mMaxBounds)) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onLowMemory() {}
+ }
}