Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce09e2ce authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Show confirmation dialog to start display mirroring

Adds support for a confirmation dialog that is shown when an external
display is connected.

Test: ConnectedDisplayInteractorTest, DisplayRepositoryTest, MirroringConfirmationDialogTest, MirroringConfirmationDialogScerenshotTest
Bug: 265060064
Change-Id: Ic0e443cd822a3c7784bd56687a244e266f2bd3f0
parent 47cd377e
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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" />
+69 −0
Original line number Diff line number Diff line
<!--
  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
+6 −0
Original line number Diff line number Diff line
@@ -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 &amp; Camera</string>
    <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
+7 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import com.android.systemui.Dependency;
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 @@ public interface SysUIComponent {
        getMediaMuteAwaitConnectionCli();
        getNearbyMediaDevicesManager();
        getUnfoldLatencyTracker().init();
        getConnectingDisplayViewModel().init();
        getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
        getFoldStateLogger().ifPresent(FoldStateLogger::init);
        getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
@@ -228,6 +230,11 @@ public interface SysUIComponent {
    /** */
    NearbyMediaDevicesManager getNearbyMediaDevicesManager();

    /**
     * Creates a ConnectingDisplayViewModel
     */
    ConnectingDisplayViewModel getConnectingDisplayViewModel();

    /**
     * Returns {@link CoreStartable}s that should be started with the application.
     */
+60 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED
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 @@ import kotlinx.coroutines.flow.stateIn
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 @@ constructor(
                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) {}
}
Loading