Snap for 11220357 from 305b00a16d to 24Q1-release
Change-Id: If8d4e64ef66ae1e5914f5479dabcda08c2fe09c8
This commit is contained in:
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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,97 +114,148 @@ 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. 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. 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.
|
||||
|
||||
1. Each display on the Client app has a "Back" and "Close" buttons. When a
|
||||
display becomes empty, it's automatically removed.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
1. 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.
|
||||
|
||||
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.
|
||||
|
||||
1. Check out the different [Host Settings](#host-settings) and
|
||||
[Client Settings](#client-settings) that allow for changing the behavior of
|
||||
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. Check out the [Demo apps](#demos) that are specifically meant to showcase
|
||||
the VDM features.
|
||||
|
||||
<!-- LINT.IfChange(host_settings) -->
|
||||
<!-- LINT.IfChange(host_options) -->
|
||||
|
||||
## Host Settings
|
||||
## Host Options
|
||||
|
||||
- **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. \
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
- 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. Run the commands below to enable this
|
||||
functionality.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
- 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.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Settings
|
||||
|
||||
#### General
|
||||
|
||||
- **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. \
|
||||
- **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 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
|
||||
```
|
||||
|
||||
#### Input method
|
||||
|
||||
- **Display IME policy**: Choose the IME behavior on remote displays. 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.vdm_custom_ime true
|
||||
adb shell am force-stop com.example.android.vdmdemo.host
|
||||
```
|
||||
|
||||
#### 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:
|
||||
@@ -213,24 +265,27 @@ available devices, build the APKs and install them.
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
<!-- LINT.ThenChange(/samples/VirtualDeviceManager/host/res/menu/settings.xml) -->
|
||||
<!-- 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 +299,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 +332,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) -->
|
||||
|
||||
@@ -56,10 +56,13 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</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" />
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
11
samples/VirtualDeviceManager/host/res/menu/options.xml
Normal file
11
samples/VirtualDeviceManager/host/res/menu/options.xml
Normal file
@@ -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) -->
|
||||
@@ -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) -->
|
||||
23
samples/VirtualDeviceManager/host/res/values/arrays.xml
Normal file
23
samples/VirtualDeviceManager/host/res/values/arrays.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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>
|
||||
|
||||
<string-array translatable="false" name="display_ime_policy_labels">
|
||||
<item>Show IME on the remote display</item>
|
||||
<item>Show IME on the default display</item>
|
||||
<item>Do not show IME</item>
|
||||
</string-array>
|
||||
<!-- Values correspond to WindowManager#DisplayImePolicy enums. -->
|
||||
<string-array translatable="false" name="display_ime_policies">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -4,15 +4,23 @@
|
||||
<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_display_ime_policy" translatable="false">display_ime_policy</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>
|
||||
110
samples/VirtualDeviceManager/host/res/xml/preferences.xml
Normal file
110
samples/VirtualDeviceManager/host/res/xml/preferences.xml
Normal file
@@ -0,0 +1,110 @@
|
||||
<?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="ime"
|
||||
android:title="Input Method"
|
||||
app:iconSpaceReserved="false">
|
||||
<ListPreference
|
||||
android:key="@string/pref_display_ime_policy"
|
||||
android:title="Display IME policy"
|
||||
android:entries="@array/display_ime_policy_labels"
|
||||
android:entryValues="@array/display_ime_policies"
|
||||
android:defaultValue="0"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
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) -->
|
||||
@@ -69,6 +69,14 @@ final class DisplayRepository {
|
||||
}
|
||||
}
|
||||
|
||||
int[] getDisplayIds() {
|
||||
synchronized (mDisplayRepository) {
|
||||
return mDisplayRepository.stream()
|
||||
.mapToInt(RemoteDisplay::getDisplayId)
|
||||
.toArray();
|
||||
}
|
||||
}
|
||||
|
||||
int[] getRemoteDisplayIds() {
|
||||
synchronized (mDisplayRepository) {
|
||||
return mDisplayRepository.stream()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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),
|
||||
|
||||
new StringRule(R.string.pref_display_ime_policy, VANILLA_ICE_CREAM, Flags::vdmCustomIme)
|
||||
.withDefaultValue(String.valueOf(0)),
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
int getInt(@StringRes int resId) {
|
||||
return Integer.valueOf(getString(resId));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -142,6 +142,9 @@ class RemoteDisplay implements AutoCloseable {
|
||||
/* executor= */ Runnable::run,
|
||||
/* callback= */ null);
|
||||
|
||||
mVirtualDevice.setDisplayImePolicy(
|
||||
getDisplayId(), mPreferenceController.getInt(R.string.pref_display_ime_policy));
|
||||
|
||||
mDpad =
|
||||
virtualDevice.createVirtualDpad(
|
||||
new VirtualDpadConfig.Builder()
|
||||
@@ -164,9 +167,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);
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,8 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -96,7 +98,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 +198,47 @@ 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_display_ime_policy,
|
||||
s -> {
|
||||
if (mVirtualDevice != null) {
|
||||
int policy = Integer.valueOf((String) s);
|
||||
Arrays.stream(mDisplayRepository.getDisplayIds()).forEach(
|
||||
displayId -> mVirtualDevice.setDisplayImePolicy(displayId, policy));
|
||||
}
|
||||
},
|
||||
|
||||
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 +259,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 +276,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 +337,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 +385,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 +480,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;
|
||||
if (mVirtualDevice != null) {
|
||||
mVirtualDevice.setDevicePolicy(
|
||||
POLICY_TYPE_RECENTS, include ? DEVICE_POLICY_DEFAULT : DEVICE_POLICY_CUSTOM);
|
||||
}
|
||||
}
|
||||
|
||||
void setCrossDeviceClipboardEnabled(boolean enabled) {
|
||||
mSettings.crossDeviceClipboardEnabled = enabled;
|
||||
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) {
|
||||
private void recreateVirtualDevice() {
|
||||
if (mVirtualDevice != null) {
|
||||
closeVirtualDevice();
|
||||
}
|
||||
settingsChange.apply();
|
||||
if (mDeviceCapabilities != null) {
|
||||
associateAndCreateVirtualDevice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDevicePolicy(int policyType, boolean custom) {
|
||||
if (mVirtualDevice != null) {
|
||||
mVirtualDevice.setDevicePolicy(
|
||||
policyType, custom ? DEVICE_POLICY_CUSTOM : DEVICE_POLICY_DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ rust_defaults {
|
||||
"libenv_logger",
|
||||
"libglob",
|
||||
"liblog_rust",
|
||||
"libnix",
|
||||
"libonce_cell",
|
||||
"libregex",
|
||||
"libserde",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
fmayle@google.com
|
||||
qwandor@google.com
|
||||
smoreland@google.com
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
use super::metadata::WorkspaceMetadata;
|
||||
use super::{Crate, CrateType, Extern, ExternType};
|
||||
use crate::CargoOutput;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
@@ -23,7 +24,6 @@ use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::collections::BTreeMap;
|
||||
use std::env;
|
||||
use std::fs::{read_to_string, File};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -31,16 +31,14 @@ use std::path::PathBuf;
|
||||
/// the rustc invocations.
|
||||
///
|
||||
/// Ignores crates outside the current directory and build script crates.
|
||||
pub fn parse_cargo_out(
|
||||
cargo_out_path: impl AsRef<Path>,
|
||||
cargo_metadata_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<Crate>> {
|
||||
let cargo_out = read_to_string(cargo_out_path).context("failed to read cargo.out")?;
|
||||
let metadata = serde_json::from_reader(
|
||||
File::open(cargo_metadata_path).context("failed to open cargo.metadata")?,
|
||||
pub fn parse_cargo_out(cargo_output: &CargoOutput) -> Result<Vec<Crate>> {
|
||||
let metadata = serde_json::from_str(&cargo_output.cargo_metadata)
|
||||
.context("failed to parse cargo metadata")?;
|
||||
parse_cargo_out_str(
|
||||
&cargo_output.cargo_out,
|
||||
&metadata,
|
||||
env::current_dir().unwrap().canonicalize().unwrap(),
|
||||
)
|
||||
.context("failed to parse cargo.metadata")?;
|
||||
parse_cargo_out_str(&cargo_out, &metadata, env::current_dir().unwrap().canonicalize().unwrap())
|
||||
}
|
||||
|
||||
/// Parses the given `cargo.out` and `cargo.metadata` file contents and generates a list of crates
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::config::VariantConfig;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -100,14 +99,9 @@ pub enum TargetKind {
|
||||
Test,
|
||||
}
|
||||
|
||||
pub fn parse_cargo_metadata_file(
|
||||
cargo_metadata_path: impl AsRef<Path>,
|
||||
cfg: &VariantConfig,
|
||||
) -> Result<Vec<Crate>> {
|
||||
let metadata: WorkspaceMetadata = serde_json::from_reader(
|
||||
File::open(cargo_metadata_path).context("failed to open cargo.metadata")?,
|
||||
)
|
||||
.context("failed to parse cargo.metadata")?;
|
||||
pub fn parse_cargo_metadata_str(cargo_metadata: &str, cfg: &VariantConfig) -> Result<Vec<Crate>> {
|
||||
let metadata =
|
||||
serde_json::from_str(cargo_metadata).context("failed to parse cargo metadata")?;
|
||||
parse_cargo_metadata(&metadata, &cfg.features, cfg.tests)
|
||||
}
|
||||
|
||||
@@ -439,7 +433,13 @@ mod tests {
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant_cfg| {
|
||||
parse_cargo_metadata_file(&cargo_metadata_path, variant_cfg).unwrap()
|
||||
parse_cargo_metadata_str(
|
||||
&read_to_string(&cargo_metadata_path)
|
||||
.with_context(|| format!("Failed to open {:?}", cargo_metadata_path))
|
||||
.unwrap(),
|
||||
variant_cfg,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(crates, expected_crates);
|
||||
|
||||
@@ -40,20 +40,23 @@ use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use bp::*;
|
||||
use cargo::{
|
||||
cargo_out::parse_cargo_out, metadata::parse_cargo_metadata_file, Crate, CrateType, ExternType,
|
||||
cargo_out::parse_cargo_out, metadata::parse_cargo_metadata_str, Crate, CrateType, ExternType,
|
||||
};
|
||||
use clap::Parser;
|
||||
use clap::Subcommand;
|
||||
use log::debug;
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::unistd::pipe2;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::env;
|
||||
use std::fs::{write, File};
|
||||
use std::io::Write;
|
||||
use std::fs::{read_to_string, write, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::os::fd::{FromRawFd, OwnedFd};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Major TODOs
|
||||
// * handle errors, esp. in cargo.out parsing. they should fail the program with an error code
|
||||
@@ -286,15 +289,22 @@ fn make_crates(args: &Args, cfg: &VariantConfig) -> Result<Vec<Crate>> {
|
||||
|
||||
let cargo_out_path = "cargo.out";
|
||||
let cargo_metadata_path = "cargo.metadata";
|
||||
if !args.reuse_cargo_out || !Path::new(cargo_out_path).exists() {
|
||||
generate_cargo_out(cfg, cargo_out_path, cargo_metadata_path)
|
||||
.context("generate_cargo_out failed")?;
|
||||
let cargo_output = if args.reuse_cargo_out && Path::new(cargo_out_path).exists() {
|
||||
CargoOutput {
|
||||
cargo_out: read_to_string(cargo_out_path)?,
|
||||
cargo_metadata: read_to_string(cargo_metadata_path)?,
|
||||
}
|
||||
} else {
|
||||
let cargo_output = generate_cargo_out(cfg).context("generate_cargo_out failed")?;
|
||||
write(cargo_out_path, &cargo_output.cargo_out)?;
|
||||
write(cargo_metadata_path, &cargo_output.cargo_metadata)?;
|
||||
cargo_output
|
||||
};
|
||||
|
||||
if cfg.run_cargo {
|
||||
parse_cargo_out(cargo_out_path, cargo_metadata_path).context("parse_cargo_out failed")
|
||||
parse_cargo_out(&cargo_output).context("parse_cargo_out failed")
|
||||
} else {
|
||||
parse_cargo_metadata_file(cargo_metadata_path, cfg)
|
||||
parse_cargo_metadata_str(&cargo_output.cargo_metadata, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,32 +385,54 @@ fn write_all_bp(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_cargo(cargo_out: &mut File, cmd: &mut Command) -> Result<()> {
|
||||
use std::os::unix::io::OwnedFd;
|
||||
use std::process::Stdio;
|
||||
let fd: OwnedFd = cargo_out.try_clone()?.into();
|
||||
/// Runs the given command, and returns its standard output and standard error as a string.
|
||||
fn run_cargo(cmd: &mut Command) -> Result<String> {
|
||||
let (pipe_read, pipe_write) = pipe()?;
|
||||
cmd.stdout(pipe_write.try_clone()?).stderr(pipe_write).stdin(Stdio::null());
|
||||
debug!("Running: {:?}\n", cmd);
|
||||
let output = cmd.stdout(Stdio::from(fd.try_clone()?)).stderr(Stdio::from(fd)).output()?;
|
||||
if !output.status.success() {
|
||||
bail!("cargo command failed with exit status: {:?}", output.status);
|
||||
let mut child = cmd.spawn()?;
|
||||
|
||||
// Unset the stdout and stderr for the command so that they are dropped in this process.
|
||||
// Otherwise the `read_to_string` below will block forever as there is still an open write file
|
||||
// descriptor for the pipe even after the child finishes.
|
||||
cmd.stderr(Stdio::null()).stdout(Stdio::null());
|
||||
|
||||
let mut output = String::new();
|
||||
File::from(pipe_read).read_to_string(&mut output)?;
|
||||
let status = child.wait()?;
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"cargo command `{:?}` failed with exit status: {:?}.\nOutput: \n------\n{}\n------",
|
||||
cmd,
|
||||
status,
|
||||
output
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Run various cargo commands and save the output to `cargo_out_path`.
|
||||
fn generate_cargo_out(
|
||||
cfg: &VariantConfig,
|
||||
cargo_out_path: &str,
|
||||
cargo_metadata_path: &str,
|
||||
) -> Result<()> {
|
||||
let mut cargo_out_file = std::fs::File::create(cargo_out_path)?;
|
||||
let mut cargo_metadata_file = std::fs::File::create(cargo_metadata_path)?;
|
||||
/// Creates a new pipe and returns the file descriptors for each end of it.
|
||||
fn pipe() -> Result<(OwnedFd, OwnedFd), nix::Error> {
|
||||
let (a, b) = pipe2(OFlag::O_CLOEXEC)?;
|
||||
// SAFETY: We just created the file descriptors, so we own them.
|
||||
unsafe { Ok((OwnedFd::from_raw_fd(a), OwnedFd::from_raw_fd(b))) }
|
||||
}
|
||||
|
||||
/// The raw output from running `cargo metadata`, `cargo build` and other commands.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct CargoOutput {
|
||||
cargo_metadata: String,
|
||||
cargo_out: String,
|
||||
}
|
||||
|
||||
/// Run various cargo commands and returns the output.
|
||||
fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
|
||||
let verbose_args = ["-v"];
|
||||
let target_dir_args = ["--target-dir", "target.tmp"];
|
||||
|
||||
// cargo clean
|
||||
run_cargo(&mut cargo_out_file, Command::new("cargo").arg("clean").args(target_dir_args))
|
||||
run_cargo(Command::new("cargo").arg("clean").args(target_dir_args))
|
||||
.context("Running cargo clean")?;
|
||||
|
||||
let default_target = "x86_64-unknown-linux-gnu";
|
||||
@@ -428,8 +460,7 @@ fn generate_cargo_out(
|
||||
};
|
||||
|
||||
// cargo metadata
|
||||
run_cargo(
|
||||
&mut cargo_metadata_file,
|
||||
let cargo_metadata = run_cargo(
|
||||
Command::new("cargo")
|
||||
.arg("metadata")
|
||||
.arg("-q") // don't output warnings to stderr
|
||||
@@ -439,10 +470,10 @@ fn generate_cargo_out(
|
||||
)
|
||||
.context("Running cargo metadata")?;
|
||||
|
||||
let mut cargo_out = String::new();
|
||||
if cfg.run_cargo {
|
||||
// cargo build
|
||||
run_cargo(
|
||||
&mut cargo_out_file,
|
||||
cargo_out += &run_cargo(
|
||||
Command::new("cargo")
|
||||
.args(["build", "--target", default_target])
|
||||
.args(verbose_args)
|
||||
@@ -453,8 +484,7 @@ fn generate_cargo_out(
|
||||
|
||||
if cfg.tests {
|
||||
// cargo build --tests
|
||||
run_cargo(
|
||||
&mut cargo_out_file,
|
||||
cargo_out += &run_cargo(
|
||||
Command::new("cargo")
|
||||
.args(["build", "--target", default_target, "--tests"])
|
||||
.args(verbose_args)
|
||||
@@ -463,8 +493,7 @@ fn generate_cargo_out(
|
||||
.args(&feature_args),
|
||||
)?;
|
||||
// cargo test -- --list
|
||||
run_cargo(
|
||||
&mut cargo_out_file,
|
||||
cargo_out += &run_cargo(
|
||||
Command::new("cargo")
|
||||
.args(["test", "--target", default_target])
|
||||
.args(target_dir_args)
|
||||
@@ -475,7 +504,7 @@ fn generate_cargo_out(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(CargoOutput { cargo_metadata, cargo_out })
|
||||
}
|
||||
|
||||
/// Create the Android.bp file for `package_dir`.
|
||||
|
||||
Reference in New Issue
Block a user