diff options
author | Vladimir Komsiyski <vladokom@google.com> | 2023-12-13 10:37:36 +0100 |
---|---|---|
committer | Vladimir Komsiyski <vladokom@google.com> | 2023-12-14 12:10:34 +0100 |
commit | 7fd26bc759ce9d9886cf9d59d3a59a5bac86ac4f (patch) | |
tree | 24e06fd132c7eae067d4e33804187c61530468ba | |
parent | 58bc3c69fdce816cf71c161dfc815870f2c8a2f6 (diff) | |
download | development-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
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); } } } |