summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Komsiyski <vladokom@google.com>2023-12-13 10:37:36 +0100
committerVladimir Komsiyski <vladokom@google.com>2023-12-14 12:10:34 +0100
commit7fd26bc759ce9d9886cf9d59d3a59a5bac86ac4f (patch)
tree24e06fd132c7eae067d4e33804187c61530468ba
parent58bc3c69fdce816cf71c161dfc815870f2c8a2f6 (diff)
downloaddevelopment-7fd26bc759ce9d9886cf9d59d3a59a5bac86ac4f.tar.gz
VDM Host dedicated settings activity.
Improved settings handling that can allow for easy disabling of features based on feature flags or SDK version Fix: 316093453 Bug: 314429442 Test: manual Change-Id: Ib530e81b106e2c25aa5e2f29e92b4bdcf4a3c29d
-rw-r--r--samples/VirtualDeviceManager/Android.bp3
-rw-r--r--samples/VirtualDeviceManager/README.md178
-rw-r--r--samples/VirtualDeviceManager/host/AndroidManifest.xml5
-rw-r--r--samples/VirtualDeviceManager/host/com.example.android.vdmdemo.host.xml3
-rw-r--r--samples/VirtualDeviceManager/host/res/layout/activity_settings.xml24
-rw-r--r--samples/VirtualDeviceManager/host/res/menu/options.xml11
-rw-r--r--samples/VirtualDeviceManager/host/res/menu/settings.xml37
-rw-r--r--samples/VirtualDeviceManager/host/res/values/arrays.xml11
-rw-r--r--samples/VirtualDeviceManager/host/res/values/strings.xml27
-rw-r--r--samples/VirtualDeviceManager/host/res/xml/preferences.xml96
-rw-r--r--samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/MainActivity.java78
-rw-r--r--samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/PreferenceController.java238
-rw-r--r--samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/RemoteDisplay.java13
-rw-r--r--samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/Settings.java46
-rw-r--r--samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/SettingsActivity.java55
-rw-r--r--samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java117
16 files changed, 628 insertions, 314 deletions
diff --git a/samples/VirtualDeviceManager/Android.bp b/samples/VirtualDeviceManager/Android.bp
index 763820d6a..e7689b848 100644
--- a/samples/VirtualDeviceManager/Android.bp
+++ b/samples/VirtualDeviceManager/Android.bp
@@ -15,8 +15,11 @@ android_app {
],
static_libs: [
"VdmCommonLib",
+ "android.companion.virtual.flags-aconfig-java",
+ "android.companion.virtualdevice.flags-aconfig-java",
"androidx.annotation_annotation",
"androidx.appcompat_appcompat",
+ "androidx.preference_preference",
"guava",
"hilt_android",
],
diff --git a/samples/VirtualDeviceManager/README.md b/samples/VirtualDeviceManager/README.md
index 1b43de14c..b2bd0056d 100644
--- a/samples/VirtualDeviceManager/README.md
+++ b/samples/VirtualDeviceManager/README.md
@@ -6,8 +6,8 @@
[Prerequisites](#prerequisites) \
[Build & Install](#build-and-install) \
[Run](#run) \
-[Host Settings](#host-settings) \
-[Client Settings](#client-settings) \
+[Host Options](#host-options) \
+[Client Options](#client-options) \
[Demos](#demos)
## Overview
@@ -71,14 +71,14 @@ available devices, build the APKs and install them.
1. Build the Host app.
- ```
+ ```shell
m -j VdmHost
```
1. Install the application as a system app on the host device.
<!-- TODO(b/314436863): Add a bash script for easy host app install. -->
- ```
+ ```shell
adb root && adb disable-verity && adb reboot # one time
adb root && adb remount
adb push $ANDROID_BUILD_TOP/development/samples/VirtualDeviceManager/host/com.example.android.vdmdemo.host.xml /system/etc/permissions/com.example.android.vdmdemo.host.xml
@@ -88,18 +88,19 @@ available devices, build the APKs and install them.
```
**Tip:** Subsequent installs without changes to permissions, etc. do not
- need all the commands above - you can just do \
+ need all the commands above - you can just run \
+ \
`adb install -r -d -g $OUT/system/priv-app/VdmHost/VdmHost.apk`
1. Build and install the Demo app on the host device.
- ```
+ ```shell
m -j VdmDemos && adb install -r -d -g $OUT/system/app/VdmDemos/VdmDemos.apk
```
1. Build and install the Client app on the client device.
- ```
+ ```shell
m -j VdmClient && adb install -r -d -g $OUT/system/app/VdmClient/VdmClient.apk
```
@@ -113,124 +114,167 @@ available devices, build the APKs and install them.
WARNING: If there are other devices in the vicinity with one of these apps
running, they might interfere.
-1. Once the connection switches to high bandwidth medium, the Host app will
- show a launcher-like list of installed apps on the host device.
+1. Check out the different [Host Options](#host-options) and
+ [Client Options](#client-options) that allow for changing the behavior of
+ the streamed apps and the virtual device in general.
-1. Clicking an app icon will create a new virtual display, launch the app there
- and start streaming the display contents to the client. The client will show
- the surface of that display and render its contents.
+1. Check out the [Demo apps](#demos) that are specifically meant to showcase
+ the VDM features.
-1. Long pressing on an app icon will open a dialog to select an existing
- display to launch the app on instead of creating a new one.
+<!-- LINT.IfChange(host_options) -->
-1. Each display on the Client app has a "Back" and "Close" buttons. When a
- display becomes empty, it's automatically removed.
+## Host Options
-1. Each display on the Client app has a "Rotate" button to switch between
- portrait and landscape orientation. This simulates the physical rotation of
- the display of the streamed activity. The "Resize" button can be used to
- change the display dimensions.
+NOTE: Any flag changes require device reboot or "Force stop" of the host app
+because the flag values are cached and evaluated only when the host app is
+starting. Alternatively, run: \
+\
+`adb shell am force-stop com.example.android.vdmdemo.host`
+
+### Launcher
-1. Each display on the Client app has a "Fullscreen" button which will move
- the contents of that display to an immersive fullscreen activity. The
- client's back button/gestures are sent back to the streamed app. Use
- Volume Down on the client device to exit fullscreen. Volume Up acts as a
- home key, if the streamed display is a home display.
+Once the connection with the client device is established, the Host app will
+show a launcher-like list of installed apps on the host device.
-1. The Host app has a "CREATE HOME DISPLAY" button, clicking it will create a
+- Clicking an app icon will create a new virtual display, launch the app there
+ and start streaming the display contents to the client. The client will show
+ the surface of that display and render its contents.
+
+- Long pressing on an app icon will open a dialog to select an existing
+ display to launch the app on instead of creating a new one.
+
+- The Host app has a **CREATE HOME DISPLAY** button, clicking it will create a
new virtual display, launch the secondary home activity there and start
streaming the display contents to the client. The display on the Client app
will have a home button, clicking it will navigate the streaming experience
- back to the home activity.
+ back to the home activity. Run the commands below to enable this
+ functionality.
-1. The Host app has a "CREATE MIRROR DISPLAY" button, clicking it will create a
- new virtual display, mirror the default host display there and start
- streaming the display contents to the client.
+ ```shell
+ adb shell device_config put virtual_devices android.companion.virtual.flags.vdm_custom_home true
+ adb shell am force-stop com.example.android.vdmdemo.host
+ ```
-1. Check out the different [Host Settings](#host-settings) and
- [Client Settings](#client-settings) that allow for changing the behavior of
- the streamed apps and the virtual device in general.
+- The Host app has a **CREATE MIRROR DISPLAY** button, clicking it will create
+ a new virtual display, mirror the default host display there and start
+ streaming the display contents to the client. Run the commands below to
+ enable this functionality.
-1. Check out the [Demo apps](#demos) that are specifically meant to showcase
- the VDM features.
+ ```shell
+ adb shell device_config put virtual_devices android.companion.virtual.flags.consistent_display_flags true
+ adb shell device_config put virtual_devices android.companion.virtual.flags.interactive_screen_mirror true
+ adb shell am force-stop com.example.android.vdmdemo.host
+ ```
-<!-- LINT.IfChange(host_settings) -->
+### Settings
-## Host Settings
+#### General
-- **Client Sensors**: Enables sensor injection from the client device into the
- host device. Any context that is associated with the virtual device will
- access the virtual sensors by default. \
+- **Device profile**: Enables device streaming CDM role as opposed to app
+ streaming role, with all differences in policies that this entails. \
*Changing this will recreate the virtual device.*
-- **Client Audio**: Enables audio output on the client device. Any context
- that is associated with the virtual device will play audio on the client by
- default. \
- *This can be changed dynamically.*
-
- **Include streamed apps in recents**: Whether streamed apps should show up
- in the host device's recent apps. Run the command below to enable this
+ in the host device's recent apps. Run the commands below to enable this
functionality. \
*This can be changed dynamically.*
```shell
adb shell device_config put virtual_devices android.companion.virtual.flags.dynamic_policy true
+ adb shell am force-stop com.example.android.vdmdemo.host
```
-- **Cross-device clipboard**: Whether to share the clipboard between the host
- and the virtual device. If disabled, both devices will have their own
- isolated clipboards. Run the command below to enable this functionality. \
+- **Enable cross-device clipboard**: Whether to share the clipboard between
+ the host and the virtual device. If disabled, both devices will have their
+ own isolated clipboards. Run the commands below to enable this
+ functionality. \
*This can be changed dynamically.*
```shell
adb shell device_config put virtual_devices android.companion.virtual.flags.cross_device_clipboard true
+ adb shell am force-stop com.example.android.vdmdemo.host
```
+#### Client capabilities
+
+- **Enable client Sensors**: Enables sensor injection from the client device
+ into the host device. Any context that is associated with the virtual device
+ will access the virtual sensors by default. \
+ *Changing this will recreate the virtual device.*
+
+- **Enable client Audio**: Enables audio output on the client device. Any
+ context that is associated with the virtual device will play audio on the
+ client by default. \
+ *This can be changed dynamically.*
+
+#### Displays
+
- **Display rotation**: Whether orientation change requests from streamed apps
should trigger orientation change of the relevant display. The client will
automatically rotate the relevant display upon such request. Disabling this
simulates a fixed orientation display that cannot physically rotate. Then
any streamed apps on that display will be letterboxed/pillarboxed if they
- request orientation change. \
+ request orientation change. Run the commands below to enable this
+ functionality. \
*This can be changed dynamically but only applies to newly created
displays.*
+ ```shell
+ adb shell device_config put virtual_devices android.companion.virtual.flags.consistent_display_flags true
+ adb shell am force-stop com.example.android.vdmdemo.host
+ ```
+
- **Always unlocked**: Whether the virtual displays should remain unlocked and
interactive when the host device is locked. Disabling this will result in a
simple lock screen shown on these displays when the host device is locked. \
*Changing this will recreate the virtual device.*
-- **Device streaming profile**: Enables device streaming CDM role as opposed
- to app streaming role, with all differences in policies that this entails. \
- *Changing this will recreate the virtual device.*
-
-- **Record encoder output**: Enables recording the output of the encoder on
- the host device to a local file on the device. This can be helpful with
- debugging Encoding related issues. To download and play the file locally:
-
- ```shell
- adb pull /sdcard/Download/vdmdemo_encoder_output_<displayId>.h264
- ffplay -f h264 vdmdemo_encoder_output_<displayId>.h264
- ```
-
- **Show pointer icon**: Whether pointer icon should be shown for virtual
input pointer devices. \
*This can be changed dynamically.*
- **Custom home**: Whether to use a custom activity as home on home displays,
- or use the device-default secondary home activity. Run the command below to
+ or use the device-default secondary home activity. Run the commands below to
enable this functionality. \
*Changing this will recreate the virtual device.*
```shell
adb shell device_config put virtual_devices android.companion.virtual.flags.vdm_custom_home true
+ adb shell am force-stop com.example.android.vdmdemo.host
```
-<!-- LINT.ThenChange(/samples/VirtualDeviceManager/host/res/menu/settings.xml) -->
+#### Debug
+
+- **Record encoder output**: Enables recording the output of the encoder on
+ the host device to a local file on the device. This can be helpful with
+ debugging Encoding related issues. To download and play the file locally:
+
+ ```shell
+ adb pull /sdcard/Download/vdmdemo_encoder_output_<displayId>.h264
+ ffplay -f h264 vdmdemo_encoder_output_<displayId>.h264
+ ```
+
+<!-- LINT.ThenChange(README.md) -->
<!-- LINT.IfChange(client_options) -->
## Client Options
+### Streamed displays
+
+- Each display on the Client app has a "Back" and "Close" buttons. When a
+ display becomes empty, it's automatically removed.
+
+- Each display on the Client app has a "Rotate" button to switch between
+ portrait and landscape orientation. This simulates the physical rotation of
+ the display of the streamed activity. The "Resize" button can be used to
+ change the display dimensions.
+
+- Each display on the Client app has a "Fullscreen" button which will move the
+ contents of that display to an immersive fullscreen activity. The client's
+ back button/gestures are sent back to the streamed app. Use Volume Down on
+ the client device to exit fullscreen. Volume Up acts as a home key, if the
+ streamed display is a home display.
+
### Input
The input menu button enables **on-screen D-Pad and touchpad** for navigating
@@ -244,7 +288,7 @@ keyboard** are forwarded to the activity streamed on the focused display.
**Externally connected mouse** events are also forwarded to the relevant
display, if the mouse pointer is currently positioned on a streamed display.
-<!-- LINT.ThenChange(/samples/VirtualDeviceManager/client/res/menu/options.xml) -->
+<!-- LINT.ThenChange(README.md) -->
<!-- LINT.IfChange(demos) -->
## Demos
@@ -277,4 +321,4 @@ display, if the mouse pointer is currently positioned on a streamed display.
is no vibration support on virtual devices, so vibration requests from
streamed activities are ignored.
-<!-- LINT.ThenChange(/samples/VirtualDeviceManager/demos/AndroidManifest.xml) -->
+<!-- LINT.ThenChange(README.md) -->
diff --git a/samples/VirtualDeviceManager/host/AndroidManifest.xml b/samples/VirtualDeviceManager/host/AndroidManifest.xml
index b9361a462..9edf07d9e 100644
--- a/samples/VirtualDeviceManager/host/AndroidManifest.xml
+++ b/samples/VirtualDeviceManager/host/AndroidManifest.xml
@@ -57,9 +57,12 @@
</intent-filter>
</activity>
<activity
+ android:name=".SettingsActivity"
+ android:exported="true"
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
+ <activity
android:name=".CustomLauncherActivity"
android:exported="true"
- android:label="@string/custom_home"
android:launchMode="singleTop"
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen" />
diff --git a/samples/VirtualDeviceManager/host/com.example.android.vdmdemo.host.xml b/samples/VirtualDeviceManager/host/com.example.android.vdmdemo.host.xml
index 43062ae5e..a2ac85a30 100644
--- a/samples/VirtualDeviceManager/host/com.example.android.vdmdemo.host.xml
+++ b/samples/VirtualDeviceManager/host/com.example.android.vdmdemo.host.xml
@@ -17,13 +17,10 @@
<permissions>
<privapp-permissions package="com.example.android.vdmdemo.host">
- <permission name="android.permission.CREATE_VIRTUAL_DEVICE" />
<permission name="android.permission.MODIFY_AUDIO_ROUTING" />
<permission name="android.permission.QUERY_AUDIO_STATE" />
<permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
<permission name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING" />
- <permission name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" />
- <permission name="android.permission.ADD_TRUSTED_DISPLAY" />
</privapp-permissions>
</permissions> \ No newline at end of file
diff --git a/samples/VirtualDeviceManager/host/res/layout/activity_settings.xml b/samples/VirtualDeviceManager/host/res/layout/activity_settings.xml
new file mode 100644
index 000000000..ab08c819b
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/res/layout/activity_settings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/main_tool_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ android:elevation="4dp"
+ android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
+ app:navigationIcon="?homeAsUpIndicator"
+ app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
+
+ <androidx.fragment.app.FragmentContainerView
+ android:id="@+id/settings_fragment_container"
+ android:name="com.example.android.vdmdemo.host.SettingsActivity$SettingsFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/samples/VirtualDeviceManager/host/res/menu/options.xml b/samples/VirtualDeviceManager/host/res/menu/options.xml
new file mode 100644
index 000000000..499ab4ddf
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/res/menu/options.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- LINT.IfChange -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item
+ android:id="@+id/settings"
+ android:icon="@drawable/settings"
+ android:title="@string/settings"
+ app:showAsAction="always" />
+</menu>
+<!-- LINT.ThenChange(/samples/VirtualDeviceManager/README.md:host_options) --> \ No newline at end of file
diff --git a/samples/VirtualDeviceManager/host/res/menu/settings.xml b/samples/VirtualDeviceManager/host/res/menu/settings.xml
deleted file mode 100644
index 698376f9a..000000000
--- a/samples/VirtualDeviceManager/host/res/menu/settings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- LINT.IfChange -->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <group android:checkableBehavior="all">
- <item
- android:id="@+id/enable_sensors"
- android:title="@string/enable_sensors" />
- <item
- android:id="@+id/enable_audio"
- android:title="@string/enable_audio" />
- <item
- android:id="@+id/enable_recents"
- android:title="@string/enable_recents" />
- <item
- android:id="@+id/enable_clipboard"
- android:title="@string/enable_clipboard" />
- <item
- android:id="@+id/enable_rotation"
- android:title="@string/enable_rotation" />
- <item
- android:id="@+id/always_unlocked"
- android:title="@string/always_unlocked" />
- <item
- android:id="@+id/use_device_streaming"
- android:title="@string/use_device_streaming" />
- <item
- android:id="@+id/record_encoder_output"
- android:title="@string/record_encoder_output" />
- <item
- android:id="@+id/show_pointer_icon"
- android:title="@string/show_pointer_icon" />
- <item
- android:id="@+id/custom_home"
- android:title="@string/custom_home" />
- </group>
-</menu>
-<!-- LINT.ThenChange(/samples/VirtualDeviceManager/README.md:host_settings) -->
diff --git a/samples/VirtualDeviceManager/host/res/values/arrays.xml b/samples/VirtualDeviceManager/host/res/values/arrays.xml
new file mode 100644
index 000000000..eed6fa18d
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/res/values/arrays.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string-array translatable="false" name="device_profile_labels">
+ <item>App streaming</item>
+ <item>Nearby device streaming</item>
+ </string-array>
+ <string-array translatable="false" name="device_profiles">
+ <item>@string/app_streaming</item>
+ <item>@string/nearby_device_streaming</item>
+ </string-array>
+</resources> \ No newline at end of file
diff --git a/samples/VirtualDeviceManager/host/res/values/strings.xml b/samples/VirtualDeviceManager/host/res/values/strings.xml
index d8966ff36..3d9533dfe 100644
--- a/samples/VirtualDeviceManager/host/res/values/strings.xml
+++ b/samples/VirtualDeviceManager/host/res/values/strings.xml
@@ -4,15 +4,22 @@
<string name="create_home_display" translatable="false">Create Home Display</string>
<string name="create_mirror_display" translatable="false">Create Mirror Display</string>
<string name="app_icon_description" translatable="false">Application Icon</string>
+ <string name="settings" translatable="false">Settings</string>
- <string name="enable_audio" translatable="false">Client audio</string>
- <string name="enable_sensors" translatable="false">Client sensors</string>
- <string name="enable_recents" translatable="false">Include streamed apps in recents</string>
- <string name="enable_clipboard" translatable="false">Cross-device clipboard</string>
- <string name="enable_rotation" translatable="false">Display rotation</string>
- <string name="always_unlocked" translatable="false">Always unlocked</string>
- <string name="use_device_streaming" translatable="false">Device streaming profile</string>
- <string name="record_encoder_output" translatable="false">Record encoder output</string>
- <string name="show_pointer_icon" translatable="false">Show pointer icon</string>
- <string name="custom_home" translatable="false">Custom home</string>
+ <string name="pref_device_profile" translatable="false">device_profile</string>
+ <string name="pref_enable_recents" translatable="false">enable_recents</string>
+ <string name="pref_enable_cross_device_clipboard" translatable="false">enable_cross_device_clipboard</string>
+ <string name="pref_enable_client_sensors" translatable="false">enable_client_sensors</string>
+ <string name="pref_enable_client_audio" translatable="false">enable_client_audio</string>
+ <string name="pref_enable_display_rotation" translatable="false">enable_display_rotation</string>
+ <string name="pref_always_unlocked_device" translatable="false">always_unlocked_device</string>
+ <string name="pref_show_pointer_icon" translatable="false">show_pointer_icon</string>
+ <string name="pref_enable_custom_home" translatable="false">enable_custom_home</string>
+ <string name="pref_record_encoder_output" translatable="false">record_encoder_output</string>
+
+ <string name="internal_pref_enable_home_displays" translatable="false">enable_home_displays</string>
+ <string name="internal_pref_enable_mirror_displays" translatable="false">enable_mirror_displays</string>
+
+ <string name="app_streaming" translatable="false">android.app.role.COMPANION_DEVICE_APP_STREAMING</string>
+ <string name="nearby_device_streaming" translatable="false">android.app.role.COMPANION_DEVICE_NEARBY_DEVICE_STREAMING</string>
</resources> \ No newline at end of file
diff --git a/samples/VirtualDeviceManager/host/res/xml/preferences.xml b/samples/VirtualDeviceManager/host/res/xml/preferences.xml
new file mode 100644
index 000000000..9151bc86a
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/res/xml/preferences.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- LINT.IfChange -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" >
+
+ <PreferenceCategory
+ android:key="general"
+ android:title="General"
+ app:iconSpaceReserved="false">
+ <ListPreference
+ android:key="@string/pref_device_profile"
+ android:title="Device profile"
+ android:entries="@array/device_profile_labels"
+ android:entryValues="@array/device_profiles"
+ android:defaultValue="@string/app_streaming"
+ app:useSimpleSummaryProvider="true"
+ app:iconSpaceReserved="false" />
+ <SwitchPreferenceCompat
+ android:key="@string/pref_enable_recents"
+ android:title="Include streamed app in recents"
+ android:defaultValue="false"
+ app:iconSpaceReserved="false"/>
+ <SwitchPreferenceCompat
+ android:key="@string/pref_enable_cross_device_clipboard"
+ android:title="Enable cross-device clipboard"
+ android:defaultValue="false"
+ app:iconSpaceReserved="false"/>
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="client_capabilities"
+ android:title="Client capabilities"
+ app:iconSpaceReserved="false">
+ <SwitchPreferenceCompat
+ android:key="@string/pref_enable_client_sensors"
+ android:title="Enable client sensors"
+ android:defaultValue="true"
+ app:iconSpaceReserved="false" />
+ <SwitchPreferenceCompat
+ android:key="@string/pref_enable_client_audio"
+ android:title="Enable client audio"
+ android:defaultValue="true"
+ app:iconSpaceReserved="false" />
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="display"
+ android:title="Displays"
+ app:iconSpaceReserved="false">
+ <SwitchPreferenceCompat
+ android:key="@string/pref_enable_display_rotation"
+ android:title="Enable display rotation"
+ android:summary="Rotate the remote display instead of letterboxing or pillarboxing"
+ android:defaultValue="true"
+ app:iconSpaceReserved="false" />
+ <SwitchPreferenceCompat
+ android:key="@string/pref_always_unlocked_device"
+ android:title="Always unlocked"
+ android:summary="Remote displays remain unlocked even when the host is locked"
+ android:defaultValue="false"
+ app:iconSpaceReserved="false" />
+ <SwitchPreferenceCompat
+ android:key="@string/pref_show_pointer_icon"
+ android:title="Show pointer icon"
+ android:summary="Mouse pointer on remote displays is visible"
+ android:defaultValue="false"
+ app:iconSpaceReserved="false" />
+ <SwitchPreferenceCompat
+ android:key="@string/pref_enable_custom_home"
+ android:title="Custom home"
+ android:summary="Use a custom home activity instead of the default one on home displays"
+ android:defaultValue="false"
+ app:iconSpaceReserved="false" />
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="debug"
+ android:title="Debug"
+ app:iconSpaceReserved="false">
+ <!--
+ When enabled, the encoder output of the host will be stored in:
+ /sdcard/Download/vdmdemo_encoder_output_[displayId].h264
+
+ After pulling this file to your machine this can be played back with:
+ ffplay -f h264 vdmdemo_encoder_output_[displayId].h264
+ -->
+ <SwitchPreferenceCompat
+ android:key="@string/pref_record_encoder_output"
+ android:title="Record encoder output"
+ android:summary="Store the host's media encoder output to a local file"
+ android:defaultValue="false"
+ app:iconSpaceReserved="false" />
+ </PreferenceCategory>
+
+</PreferenceScreen>
+<!-- LINT.ThenChange(/samples/VirtualDeviceManager/README.md:host_options) -->
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/MainActivity.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/MainActivity.java
index f594e743e..b397100d7 100644
--- a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/MainActivity.java
+++ b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/MainActivity.java
@@ -84,7 +84,7 @@ public class MainActivity extends Hilt_MainActivity {
};
@Inject ConnectionManager mConnectionManager;
- @Inject Settings mSettings;
+ @Inject PreferenceController mPreferenceController;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -92,11 +92,15 @@ public class MainActivity extends Hilt_MainActivity {
setContentView(R.layout.activity_main);
Toolbar toolbar = requireViewById(R.id.main_tool_bar);
- toolbar.setOverflowIcon(getDrawable(R.drawable.settings));
setSupportActionBar(toolbar);
mHomeDisplayButton = requireViewById(R.id.create_home_display);
+ mHomeDisplayButton.setEnabled(
+ mPreferenceController.getBoolean(R.string.internal_pref_enable_home_displays));
mMirrorDisplayButton = requireViewById(R.id.create_mirror_display);
+ mMirrorDisplayButton.setEnabled(
+ mPreferenceController.getBoolean(R.string.internal_pref_enable_mirror_displays));
+
mLauncher = requireViewById(R.id.app_grid);
mLauncher.setVisibility(View.GONE);
LauncherAdapter launcherAdapter = new LauncherAdapter(getPackageManager());
@@ -189,79 +193,15 @@ public class MainActivity extends Hilt_MainActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.settings, menu);
- for (int i = 0; i < menu.size(); ++i) {
- MenuItem item = menu.getItem(i);
- switch (item.getItemId()) {
- case R.id.enable_sensors:
- item.setChecked(mSettings.sensorsEnabled);
- break;
- case R.id.enable_audio:
- item.setChecked(mSettings.audioEnabled);
- break;
- case R.id.enable_recents:
- item.setChecked(mSettings.includeInRecents);
- break;
- case R.id.enable_clipboard:
- item.setChecked(mSettings.crossDeviceClipboardEnabled);
- break;
- case R.id.enable_rotation:
- item.setChecked(mSettings.displayRotationEnabled);
- break;
- case R.id.always_unlocked:
- item.setChecked(mSettings.alwaysUnlocked);
- break;
- case R.id.use_device_streaming:
- item.setChecked(mSettings.deviceStreaming);
- break;
- case R.id.show_pointer_icon:
- item.setChecked(mSettings.showPointerIcon);
- break;
- case R.id.record_encoder_output:
- item.setChecked(mSettings.recordEncoderOutput);
- break;
- case R.id.custom_home:
- item.setChecked(mSettings.customHome);
- break;
- }
- }
+ inflater.inflate(R.menu.options, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- item.setChecked(!item.isChecked());
-
switch (item.getItemId()) {
- case R.id.enable_sensors:
- mVdmService.setSensorsEnabled(item.isChecked());
- return true;
- case R.id.enable_audio:
- mVdmService.setAudioEnabled(item.isChecked());
- return true;
- case R.id.enable_recents:
- mVdmService.setIncludeInRecents(item.isChecked());
- return true;
- case R.id.enable_clipboard:
- mVdmService.setCrossDeviceClipboardEnabled(item.isChecked());
- return true;
- case R.id.enable_rotation:
- mVdmService.setDisplayRotationEnabled(item.isChecked());
- return true;
- case R.id.always_unlocked:
- mVdmService.setAlwaysUnlocked(item.isChecked());
- return true;
- case R.id.use_device_streaming:
- mVdmService.setDeviceStreaming(item.isChecked());
- return true;
- case R.id.record_encoder_output:
- mVdmService.setRecordEncoderOutput(item.isChecked());
- return true;
- case R.id.show_pointer_icon:
- mVdmService.setShowPointerIcon(item.isChecked());
- return true;
- case R.id.custom_home:
- mVdmService.setCustomHome(item.isChecked());
+ case R.id.settings:
+ startActivity(new Intent(this, SettingsActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/PreferenceController.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/PreferenceController.java
new file mode 100644
index 000000000..a6f4f931e
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/PreferenceController.java
@@ -0,0 +1,238 @@
+/*
+ * 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.example.android.vdmdemo.host;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM;
+
+import android.companion.AssociationRequest;
+import android.companion.virtual.flags.Flags;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.ArrayMap;
+
+import androidx.annotation.StringRes;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+
+import dagger.hilt.android.qualifiers.ApplicationContext;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages the VDM Demo Host application settings and feature switches.
+ *
+ * <p>Upon creation, it will automatically update the preference values based on the current SDK
+ * version and the relevant feature flags.</p>
+ */
+@Singleton
+final class PreferenceController {
+
+ // LINT.IfChange
+ private static final Set<PrefRule<?>> RULES = Set.of(
+
+ // Exposed in the settings page
+
+ new BoolRule(R.string.pref_enable_cross_device_clipboard,
+ VANILLA_ICE_CREAM, Flags::crossDeviceClipboard),
+
+ new BoolRule(R.string.pref_enable_client_sensors, UPSIDE_DOWN_CAKE),
+
+ new BoolRule(R.string.pref_enable_display_rotation,
+ VANILLA_ICE_CREAM, Flags::consistentDisplayFlags)
+ .withDefaultValue(true),
+
+ new BoolRule(R.string.pref_enable_custom_home, VANILLA_ICE_CREAM, Flags::vdmCustomHome),
+
+ // TODO(b/316098039): Evaluate the minSdk of the prefs below.
+ new StringRule(R.string.pref_device_profile, VANILLA_ICE_CREAM)
+ .withDefaultValue(AssociationRequest.DEVICE_PROFILE_APP_STREAMING),
+ new BoolRule(R.string.pref_enable_recents, VANILLA_ICE_CREAM),
+ new BoolRule(R.string.pref_enable_client_audio, VANILLA_ICE_CREAM),
+ new BoolRule(R.string.pref_always_unlocked_device, VANILLA_ICE_CREAM),
+ new BoolRule(R.string.pref_show_pointer_icon, VANILLA_ICE_CREAM),
+ new BoolRule(R.string.pref_record_encoder_output, VANILLA_ICE_CREAM),
+
+ // Internal-only switches not exposed in the settings page.
+ // All of these are booleans acting as switches, while the above ones may be any type.
+
+ // TODO(b/316098039): Use the SysDecor flag on <= VIC
+ new InternalBoolRule(R.string.internal_pref_enable_home_displays,
+ VANILLA_ICE_CREAM, Flags::vdmCustomHome),
+
+ new InternalBoolRule(R.string.internal_pref_enable_mirror_displays,
+ VANILLA_ICE_CREAM,
+ Flags::consistentDisplayFlags, Flags::interactiveScreenMirror)
+ );
+ // LINT.ThenChange(/samples/VirtualDeviceManager/README.md:host_options)
+
+ private final ArrayMap<Object, Map<String, Consumer<Object>>> mObservers = new ArrayMap<>();
+ private final SharedPreferences.OnSharedPreferenceChangeListener mPreferenceChangeListener =
+ this::onPreferencesChanged;
+
+ private final Context mContext;
+ private final SharedPreferences mSharedPreferences;
+
+ @Inject
+ PreferenceController(@ApplicationContext Context context) {
+ mContext = context;
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
+
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ RULES.forEach(r -> r.evaluate(mContext, editor));
+ editor.commit();
+
+ mSharedPreferences.registerOnSharedPreferenceChangeListener(mPreferenceChangeListener);
+ }
+
+ /**
+ * Adds an observer for preference changes.
+ *
+ * @param key an object used only for bookkeeping.
+ * @param preferenceObserver a map from resource ID corresponding to the preference string key
+ * to the function that should be executed when that preference changes.
+ */
+ void addPreferenceObserver(Object key, Map<Integer, Consumer<Object>> preferenceObserver) {
+ ArrayMap<String, Consumer<Object>> stringObserver = new ArrayMap<>();
+ for (int resId : preferenceObserver.keySet()) {
+ stringObserver.put(
+ Objects.requireNonNull(mContext.getString(resId)),
+ preferenceObserver.get(resId));
+ }
+ mObservers.put(key, stringObserver);
+ }
+
+ /** Removes a previously added preference observer for the given key. */
+ void removePreferenceObserver(Object key) {
+ mObservers.remove(key);
+ }
+
+ /**
+ * Disables any {@link androidx.preference.Preference}, which is not satisfied by the current
+ * SDK version or the relevant feature flags.
+ *
+ * <p>This doesn't change any of the preference values, only disables the relevant UI elements
+ * in the preference screen.</p>
+ */
+ void evaluate(PreferenceManager preferenceManager) {
+ RULES.forEach(r -> r.evaluate(mContext, preferenceManager));
+ }
+
+ boolean getBoolean(@StringRes int resId) {
+ return mSharedPreferences.getBoolean(mContext.getString(resId), false);
+ }
+
+ String getString(@StringRes int resId) {
+ return Objects.requireNonNull(
+ mSharedPreferences.getString(mContext.getString(resId), null));
+ }
+
+ private void onPreferencesChanged(SharedPreferences sharedPreferences, String key) {
+ Map<String, ?> currentPreferences = sharedPreferences.getAll();
+ for (Map<String, Consumer<Object>> observer : mObservers.values()) {
+ Consumer<Object> consumer = observer.get(key);
+ if (consumer != null) {
+ consumer.accept(currentPreferences.get(key));
+ }
+ }
+ }
+
+ private abstract static class PrefRule<T> {
+ final @StringRes int mKey;
+ final int mMinSdk;
+ final BooleanSupplier[] mRequiredFlags;
+
+ protected T mDefaultValue;
+
+ PrefRule(@StringRes int key, T defaultValue, int minSdk, BooleanSupplier... requiredFlags) {
+ mKey = key;
+ mMinSdk = minSdk;
+ mRequiredFlags = requiredFlags;
+ mDefaultValue = defaultValue;
+ }
+
+ void evaluate(Context context, SharedPreferences.Editor editor) {
+ if (!isSatisfied()) {
+ reset(context, editor);
+ }
+ }
+
+ void evaluate(Context context, PreferenceManager preferenceManager) {
+ Preference preference = preferenceManager.findPreference(context.getString(mKey));
+ if (preference != null) {
+ boolean enabled = isSatisfied();
+ if (preference.isEnabled() != enabled) {
+ preference.setEnabled(enabled);
+ }
+ }
+ }
+
+ protected abstract void reset(Context context, SharedPreferences.Editor editor);
+
+ protected boolean isSatisfied() {
+ return mMinSdk >= SDK_INT
+ && Arrays.stream(mRequiredFlags).allMatch(BooleanSupplier::getAsBoolean);
+ }
+
+ PrefRule<T> withDefaultValue(T defaultValue) {
+ mDefaultValue = defaultValue;
+ return this;
+ }
+ }
+
+ private static class BoolRule extends PrefRule<Boolean> {
+ BoolRule(@StringRes int key, int minSdk, BooleanSupplier... requiredFlags) {
+ super(key, false, minSdk, requiredFlags);
+ }
+
+ @Override
+ protected void reset(Context context, SharedPreferences.Editor editor) {
+ editor.putBoolean(context.getString(mKey), mDefaultValue);
+ }
+ }
+
+ private static class InternalBoolRule extends BoolRule {
+ InternalBoolRule(@StringRes int key, int minSdk, BooleanSupplier... requiredFlags) {
+ super(key, minSdk, requiredFlags);
+ }
+
+ @Override
+ void evaluate(Context context, SharedPreferences.Editor editor) {
+ editor.putBoolean(context.getString(mKey), isSatisfied());
+ }
+ }
+
+ private static class StringRule extends PrefRule<String> {
+ StringRule(@StringRes int key, int minSdk, BooleanSupplier... requiredFlags) {
+ super(key, null, minSdk, requiredFlags);
+ }
+
+ @Override
+ protected void reset(Context context, SharedPreferences.Editor editor) {
+ editor.putString(context.getString(mKey), mDefaultValue);
+ }
+ }
+}
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/RemoteDisplay.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/RemoteDisplay.java
index 1e805f1cf..a235f64af 100644
--- a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/RemoteDisplay.java
+++ b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/RemoteDisplay.java
@@ -86,7 +86,7 @@ class RemoteDisplay implements AutoCloseable {
private final Context mContext;
private final RemoteIo mRemoteIo;
- private final Settings mSettings;
+ private final PreferenceController mPreferenceController;
private final Consumer<RemoteEvent> mRemoteEventConsumer = this::processRemoteEvent;
private final VirtualDisplay mVirtualDisplay;
private final VirtualDpad mDpad;
@@ -113,19 +113,19 @@ class RemoteDisplay implements AutoCloseable {
VirtualDevice virtualDevice,
RemoteIo remoteIo,
@DisplayType int displayType,
- Settings settings) {
+ PreferenceController preferenceController) {
mContext = context;
mRemoteIo = remoteIo;
mRemoteDisplayId = event.getDisplayId();
mVirtualDevice = virtualDevice;
mPendingIntentExecutor = context.getMainExecutor();
mDisplayType = displayType;
- mSettings = settings;
+ mPreferenceController = preferenceController;
setCapabilities(event.getDisplayCapabilities());
int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
- if (settings.displayRotationEnabled) {
+ if (mPreferenceController.getBoolean(R.string.pref_enable_display_rotation)) {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
}
if (mDisplayType == DISPLAY_TYPE_MIRROR) {
@@ -164,9 +164,8 @@ class RemoteDisplay implements AutoCloseable {
if (mVideoManager != null) {
mVideoManager.stop();
}
- mVideoManager =
- VideoManager.createEncoder(
- mRemoteDisplayId, mRemoteIo, mSettings.recordEncoderOutput);
+ mVideoManager = VideoManager.createEncoder(mRemoteDisplayId, mRemoteIo,
+ mPreferenceController.getBoolean(R.string.pref_record_encoder_output));
Surface surface = mVideoManager.createInputSurface(mWidth, mHeight, DISPLAY_FPS);
mVirtualDisplay.setSurface(surface);
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/Settings.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/Settings.java
deleted file mode 100644
index 0eaebc59a..000000000
--- a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/Settings.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.example.android.vdmdemo.host;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/** Settings known to the VDM Demo Host application */
-@Singleton
-final class Settings {
- public boolean displayRotationEnabled = true;
- public boolean sensorsEnabled = true;
- public boolean audioEnabled = true;
- public boolean includeInRecents = false;
- public boolean crossDeviceClipboardEnabled = false;
- public boolean alwaysUnlocked = true;
- public boolean deviceStreaming = false;
- public boolean showPointerIcon = true;
- public boolean customHome = false;
-
- /**
- * When enabled, the encoder output of the host will be stored in:
- * /sdcard/Download/vdmdemo_encoder_output_[displayId].h264
- *
- * <p>After pulling this file to your machine this can be played back with:
- * {@code ffplay -f h264 vdmdemo_encoder_output_[displayId].h264}
- */
- public boolean recordEncoderOutput = false;
-
- @Inject
- Settings() {}
-}
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/SettingsActivity.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/SettingsActivity.java
new file mode 100644
index 000000000..3cc1aaf62
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/SettingsActivity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.example.android.vdmdemo.host;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.preference.PreferenceFragmentCompat;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+import javax.inject.Inject;
+
+/** VDM Host Settings activity. */
+@AndroidEntryPoint(AppCompatActivity.class)
+public class SettingsActivity extends Hilt_SettingsActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_settings);
+ Toolbar toolbar = requireViewById(R.id.main_tool_bar);
+ setSupportActionBar(toolbar);
+ toolbar.setNavigationOnClickListener(v -> finish());
+ setTitle(getTitle() + " " + getString(R.string.settings));
+ }
+
+ @AndroidEntryPoint(PreferenceFragmentCompat.class)
+ public static final class SettingsFragment extends Hilt_SettingsActivity_SettingsFragment {
+
+ @Inject PreferenceController mPreferenceController;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.preferences, rootKey);
+ mPreferenceController.evaluate(getPreferenceManager());
+ }
+ }
+}
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java
index 53c6d1d70..cae06f7e5 100644
--- a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java
+++ b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java
@@ -63,6 +63,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import dagger.hilt.android.AndroidEntryPoint;
+import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
@@ -96,7 +97,7 @@ public final class VdmService extends Hilt_VdmService {
@Inject ConnectionManager mConnectionManager;
@Inject RemoteIo mRemoteIo;
@Inject AudioStreamer mAudioStreamer;
- @Inject Settings mSettings;
+ @Inject PreferenceController mPreferenceController;
@Inject DisplayRepository mDisplayRepository;
private RemoteSensorManager mRemoteSensorManager = null;
@@ -196,14 +197,38 @@ public final class VdmService extends Hilt_VdmService {
mRemoteIo.addMessageConsumer(mRemoteEventConsumer);
- if (mSettings.audioEnabled) {
+ if (mPreferenceController.getBoolean(R.string.pref_enable_client_audio)) {
mAudioStreamer.start();
}
+
+ mPreferenceController.addPreferenceObserver(this, Map.of(
+ R.string.pref_enable_recents,
+ b -> updateDevicePolicy(POLICY_TYPE_RECENTS, !(Boolean) b),
+
+ R.string.pref_enable_cross_device_clipboard,
+ b -> updateDevicePolicy(POLICY_TYPE_CLIPBOARD, (Boolean) b),
+
+ R.string.pref_show_pointer_icon,
+ b -> {
+ if (mVirtualDevice != null) mVirtualDevice.setShowPointerIcon((Boolean) b);
+ },
+
+ R.string.pref_enable_client_audio,
+ b -> {
+ if ((Boolean) b) mAudioStreamer.start(); else mAudioStreamer.stop();
+ },
+
+ R.string.pref_enable_client_sensors, v -> recreateVirtualDevice(),
+ R.string.pref_device_profile, v -> recreateVirtualDevice(),
+ R.string.pref_always_unlocked_device, v -> recreateVirtualDevice(),
+ R.string.pref_enable_custom_home, v -> recreateVirtualDevice()
+ ));
}
@Override
public void onDestroy() {
super.onDestroy();
+ mPreferenceController.removePreferenceObserver(this);
mConnectionManager.removeConnectionCallback(mConnectionCallback);
closeVirtualDevice();
mRemoteIo.removeMessageConsumer(mRemoteEventConsumer);
@@ -224,7 +249,7 @@ public final class VdmService extends Hilt_VdmService {
mVirtualDevice,
mRemoteIo,
mPendingDisplayType,
- mSettings);
+ mPreferenceController);
mDisplayRepository.addDisplay(remoteDisplay);
mPendingDisplayType = RemoteDisplay.DISPLAY_TYPE_APP;
if (mPendingRemoteIntent != null) {
@@ -241,10 +266,7 @@ public final class VdmService extends Hilt_VdmService {
CompanionDeviceManager cdm =
Objects.requireNonNull(getSystemService(CompanionDeviceManager.class));
RoleManager rm = Objects.requireNonNull(getSystemService(RoleManager.class));
- final String deviceProfile =
- mSettings.deviceStreaming
- ? AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING
- : AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
+ final String deviceProfile = mPreferenceController.getString(R.string.pref_device_profile);
for (AssociationInfo associationInfo : cdm.getMyAssociations()) {
// Flashing the device clears the role and the permissions, but not the CDM
// associations.
@@ -305,24 +327,24 @@ public final class VdmService extends Hilt_VdmService {
.setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
.setAudioPlaybackSessionId(mAudioStreamer.getPlaybackSessionId());
- if (mSettings.alwaysUnlocked) {
+ if (mPreferenceController.getBoolean(R.string.pref_always_unlocked_device)) {
virtualDeviceBuilder.setLockState(LOCK_STATE_ALWAYS_UNLOCKED);
}
- if (mSettings.customHome) {
+ if (mPreferenceController.getBoolean(R.string.pref_enable_custom_home)) {
virtualDeviceBuilder.setHomeComponent(
new ComponentName(this, CustomLauncherActivity.class));
}
- if (!mSettings.includeInRecents) {
+ if (!mPreferenceController.getBoolean(R.string.pref_enable_recents)) {
virtualDeviceBuilder.setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_CUSTOM);
}
- if (mSettings.crossDeviceClipboardEnabled) {
+ if (mPreferenceController.getBoolean(R.string.pref_enable_cross_device_clipboard)) {
virtualDeviceBuilder.setDevicePolicy(POLICY_TYPE_CLIPBOARD, DEVICE_POLICY_CUSTOM);
}
- if (mSettings.sensorsEnabled) {
+ if (mPreferenceController.getBoolean(R.string.pref_enable_client_sensors)) {
for (SensorCapabilities sensor : mDeviceCapabilities.getSensorCapabilitiesList()) {
virtualDeviceBuilder.addVirtualSensorConfig(
new VirtualSensorConfig.Builder(
@@ -353,7 +375,8 @@ public final class VdmService extends Hilt_VdmService {
mRemoteSensorManager.setVirtualSensors(mVirtualDevice.getVirtualSensorList());
}
- mVirtualDevice.setShowPointerIcon(mSettings.showPointerIcon);
+ mVirtualDevice.setShowPointerIcon(
+ mPreferenceController.getBoolean(R.string.pref_show_pointer_icon));
mVirtualDevice.addActivityListener(
MoreExecutors.directExecutor(),
@@ -447,73 +470,19 @@ public final class VdmService extends Hilt_VdmService {
.ifPresent(d -> d.launchIntent(pendingIntent));
}
- void setDisplayRotationEnabled(boolean enabled) {
- mSettings.displayRotationEnabled = enabled;
- }
-
- void setSensorsEnabled(boolean enabled) {
- recreateVirtualDevice(() -> mSettings.sensorsEnabled = enabled);
- }
-
- void setIncludeInRecents(boolean include) {
- mSettings.includeInRecents = include;
+ private void recreateVirtualDevice() {
if (mVirtualDevice != null) {
- mVirtualDevice.setDevicePolicy(
- POLICY_TYPE_RECENTS, include ? DEVICE_POLICY_DEFAULT : DEVICE_POLICY_CUSTOM);
+ closeVirtualDevice();
+ if (mDeviceCapabilities != null) {
+ associateAndCreateVirtualDevice();
+ }
}
}
- void setCrossDeviceClipboardEnabled(boolean enabled) {
- mSettings.crossDeviceClipboardEnabled = enabled;
+ private void updateDevicePolicy(int policyType, boolean custom) {
if (mVirtualDevice != null) {
mVirtualDevice.setDevicePolicy(
- POLICY_TYPE_CLIPBOARD, enabled ? DEVICE_POLICY_CUSTOM : DEVICE_POLICY_DEFAULT);
- }
- }
-
- void setAlwaysUnlocked(boolean enabled) {
- recreateVirtualDevice(() -> mSettings.alwaysUnlocked = enabled);
- }
-
- void setDeviceStreaming(boolean enabled) {
- recreateVirtualDevice(() -> mSettings.deviceStreaming = enabled);
- }
-
- void setRecordEncoderOutput(boolean enabled) {
- recreateVirtualDevice(() -> mSettings.recordEncoderOutput = enabled);
- }
-
- void setShowPointerIcon(boolean enabled) {
- mSettings.showPointerIcon = enabled;
- if (mVirtualDevice != null) {
- mVirtualDevice.setShowPointerIcon(enabled);
- }
- }
-
- void setAudioEnabled(boolean enabled) {
- mSettings.audioEnabled = enabled;
- if (enabled) {
- mAudioStreamer.start();
- } else {
- mAudioStreamer.stop();
- }
- }
-
- void setCustomHome(boolean enabled) {
- recreateVirtualDevice(() -> mSettings.customHome = enabled);
- }
-
- private interface DeviceSettingsChange {
- void apply();
- }
-
- private void recreateVirtualDevice(DeviceSettingsChange settingsChange) {
- if (mVirtualDevice != null) {
- closeVirtualDevice();
- }
- settingsChange.apply();
- if (mDeviceCapabilities != null) {
- associateAndCreateVirtualDevice();
+ policyType, custom ? DEVICE_POLICY_CUSTOM : DEVICE_POLICY_DEFAULT);
}
}
}