Snap for 11220357 from 305b00a16d to 24Q1-release
Change-Id: If8d4e64ef66ae1e5914f5479dabcda08c2fe09c8
This commit is contained in:
@@ -15,8 +15,11 @@ android_app {
|
|||||||
],
|
],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"VdmCommonLib",
|
"VdmCommonLib",
|
||||||
|
"android.companion.virtual.flags-aconfig-java",
|
||||||
|
"android.companion.virtualdevice.flags-aconfig-java",
|
||||||
"androidx.annotation_annotation",
|
"androidx.annotation_annotation",
|
||||||
"androidx.appcompat_appcompat",
|
"androidx.appcompat_appcompat",
|
||||||
|
"androidx.preference_preference",
|
||||||
"guava",
|
"guava",
|
||||||
"hilt_android",
|
"hilt_android",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
[Prerequisites](#prerequisites) \
|
[Prerequisites](#prerequisites) \
|
||||||
[Build & Install](#build-and-install) \
|
[Build & Install](#build-and-install) \
|
||||||
[Run](#run) \
|
[Run](#run) \
|
||||||
[Host Settings](#host-settings) \
|
[Host Options](#host-options) \
|
||||||
[Client Settings](#client-settings) \
|
[Client Options](#client-options) \
|
||||||
[Demos](#demos)
|
[Demos](#demos)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@@ -71,14 +71,14 @@ available devices, build the APKs and install them.
|
|||||||
|
|
||||||
1. Build the Host app.
|
1. Build the Host app.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
m -j VdmHost
|
m -j VdmHost
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Install the application as a system app on the host device.
|
1. Install the application as a system app on the host device.
|
||||||
<!-- TODO(b/314436863): Add a bash script for easy host app install. -->
|
<!-- 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 disable-verity && adb reboot # one time
|
||||||
adb root && adb remount
|
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
|
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
|
**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`
|
`adb install -r -d -g $OUT/system/priv-app/VdmHost/VdmHost.apk`
|
||||||
|
|
||||||
1. Build and install the Demo app on the host device.
|
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
|
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.
|
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
|
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
|
WARNING: If there are other devices in the vicinity with one of these apps
|
||||||
running, they might interfere.
|
running, they might interfere.
|
||||||
|
|
||||||
1. Once the connection switches to high bandwidth medium, the Host app will
|
1. Check out the different [Host Options](#host-options) and
|
||||||
show a launcher-like list of installed apps on the host device.
|
[Client Options](#client-options) that allow for changing the behavior of
|
||||||
|
|
||||||
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
|
|
||||||
the streamed apps and the virtual device in general.
|
the streamed apps and the virtual device in general.
|
||||||
|
|
||||||
1. Check out the [Demo apps](#demos) that are specifically meant to showcase
|
1. Check out the [Demo apps](#demos) that are specifically meant to showcase
|
||||||
the VDM features.
|
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
|
NOTE: Any flag changes require device reboot or "Force stop" of the host app
|
||||||
host device. Any context that is associated with the virtual device will
|
because the flag values are cached and evaluated only when the host app is
|
||||||
access the virtual sensors by default. \
|
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.*
|
*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
|
- **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. \
|
functionality. \
|
||||||
*This can be changed dynamically.*
|
*This can be changed dynamically.*
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
adb shell device_config put virtual_devices android.companion.virtual.flags.dynamic_policy true
|
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
|
- **Enable cross-device clipboard**: Whether to share the clipboard between
|
||||||
and the virtual device. If disabled, both devices will have their own
|
the host and the virtual device. If disabled, both devices will have their
|
||||||
isolated clipboards. Run the command below to enable this functionality. \
|
own isolated clipboards. Run the commands below to enable this
|
||||||
|
functionality. \
|
||||||
*This can be changed dynamically.*
|
*This can be changed dynamically.*
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
adb shell device_config put virtual_devices android.companion.virtual.flags.cross_device_clipboard true
|
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
|
- **Display rotation**: Whether orientation change requests from streamed apps
|
||||||
should trigger orientation change of the relevant display. The client will
|
should trigger orientation change of the relevant display. The client will
|
||||||
automatically rotate the relevant display upon such request. Disabling this
|
automatically rotate the relevant display upon such request. Disabling this
|
||||||
simulates a fixed orientation display that cannot physically rotate. Then
|
simulates a fixed orientation display that cannot physically rotate. Then
|
||||||
any streamed apps on that display will be letterboxed/pillarboxed if they
|
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
|
*This can be changed dynamically but only applies to newly created
|
||||||
displays.*
|
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
|
- **Always unlocked**: Whether the virtual displays should remain unlocked and
|
||||||
interactive when the host device is locked. Disabling this will result in a
|
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. \
|
simple lock screen shown on these displays when the host device is locked. \
|
||||||
*Changing this will recreate the virtual device.*
|
*Changing this will recreate the virtual device.*
|
||||||
|
|
||||||
- **Device streaming profile**: Enables device streaming CDM role as opposed
|
- **Show pointer icon**: Whether pointer icon should be shown for virtual
|
||||||
to app streaming role, with all differences in policies that this entails. \
|
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.*
|
*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
|
- **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
|
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:
|
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
|
ffplay -f h264 vdmdemo_encoder_output_<displayId>.h264
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Show pointer icon**: Whether pointer icon should be shown for virtual
|
<!-- LINT.ThenChange(README.md) -->
|
||||||
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.IfChange(client_options) -->
|
<!-- LINT.IfChange(client_options) -->
|
||||||
|
|
||||||
## 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
|
### Input
|
||||||
|
|
||||||
The input menu button enables **on-screen D-Pad and touchpad** for navigating
|
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
|
**Externally connected mouse** events are also forwarded to the relevant
|
||||||
display, if the mouse pointer is currently positioned on a streamed display.
|
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) -->
|
<!-- LINT.IfChange(demos) -->
|
||||||
|
|
||||||
## 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
|
is no vibration support on virtual devices, so vibration requests from
|
||||||
streamed activities are ignored.
|
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" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".CustomLauncherActivity"
|
android:name=".CustomLauncherActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/custom_home"
|
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen" />
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen" />
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,10 @@
|
|||||||
|
|
||||||
<permissions>
|
<permissions>
|
||||||
<privapp-permissions package="com.example.android.vdmdemo.host">
|
<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.MODIFY_AUDIO_ROUTING" />
|
||||||
<permission name="android.permission.QUERY_AUDIO_STATE" />
|
<permission name="android.permission.QUERY_AUDIO_STATE" />
|
||||||
<permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
|
<permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
|
||||||
<permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
|
<permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
|
||||||
<permission name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_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>
|
</privapp-permissions>
|
||||||
</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_home_display" translatable="false">Create Home Display</string>
|
||||||
<string name="create_mirror_display" translatable="false">Create Mirror 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="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="pref_device_profile" translatable="false">device_profile</string>
|
||||||
<string name="enable_sensors" translatable="false">Client sensors</string>
|
<string name="pref_enable_recents" translatable="false">enable_recents</string>
|
||||||
<string name="enable_recents" translatable="false">Include streamed apps in recents</string>
|
<string name="pref_enable_cross_device_clipboard" translatable="false">enable_cross_device_clipboard</string>
|
||||||
<string name="enable_clipboard" translatable="false">Cross-device clipboard</string>
|
<string name="pref_enable_client_sensors" translatable="false">enable_client_sensors</string>
|
||||||
<string name="enable_rotation" translatable="false">Display rotation</string>
|
<string name="pref_enable_client_audio" translatable="false">enable_client_audio</string>
|
||||||
<string name="always_unlocked" translatable="false">Always unlocked</string>
|
<string name="pref_enable_display_rotation" translatable="false">enable_display_rotation</string>
|
||||||
<string name="use_device_streaming" translatable="false">Device streaming profile</string>
|
<string name="pref_always_unlocked_device" translatable="false">always_unlocked_device</string>
|
||||||
<string name="record_encoder_output" translatable="false">Record encoder output</string>
|
<string name="pref_show_pointer_icon" translatable="false">show_pointer_icon</string>
|
||||||
<string name="show_pointer_icon" translatable="false">Show pointer icon</string>
|
<string name="pref_enable_custom_home" translatable="false">enable_custom_home</string>
|
||||||
<string name="custom_home" translatable="false">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>
|
</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() {
|
int[] getRemoteDisplayIds() {
|
||||||
synchronized (mDisplayRepository) {
|
synchronized (mDisplayRepository) {
|
||||||
return mDisplayRepository.stream()
|
return mDisplayRepository.stream()
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ public class MainActivity extends Hilt_MainActivity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Inject ConnectionManager mConnectionManager;
|
@Inject ConnectionManager mConnectionManager;
|
||||||
@Inject Settings mSettings;
|
@Inject PreferenceController mPreferenceController;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -92,11 +92,15 @@ public class MainActivity extends Hilt_MainActivity {
|
|||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
Toolbar toolbar = requireViewById(R.id.main_tool_bar);
|
Toolbar toolbar = requireViewById(R.id.main_tool_bar);
|
||||||
toolbar.setOverflowIcon(getDrawable(R.drawable.settings));
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
mHomeDisplayButton = requireViewById(R.id.create_home_display);
|
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 = requireViewById(R.id.create_mirror_display);
|
||||||
|
mMirrorDisplayButton.setEnabled(
|
||||||
|
mPreferenceController.getBoolean(R.string.internal_pref_enable_mirror_displays));
|
||||||
|
|
||||||
mLauncher = requireViewById(R.id.app_grid);
|
mLauncher = requireViewById(R.id.app_grid);
|
||||||
mLauncher.setVisibility(View.GONE);
|
mLauncher.setVisibility(View.GONE);
|
||||||
LauncherAdapter launcherAdapter = new LauncherAdapter(getPackageManager());
|
LauncherAdapter launcherAdapter = new LauncherAdapter(getPackageManager());
|
||||||
@@ -189,79 +193,15 @@ public class MainActivity extends Hilt_MainActivity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.settings, menu);
|
inflater.inflate(R.menu.options, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
item.setChecked(!item.isChecked());
|
|
||||||
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.enable_sensors:
|
case R.id.settings:
|
||||||
mVdmService.setSensorsEnabled(item.isChecked());
|
startActivity(new Intent(this, SettingsActivity.class));
|
||||||
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());
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
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 Context mContext;
|
||||||
private final RemoteIo mRemoteIo;
|
private final RemoteIo mRemoteIo;
|
||||||
private final Settings mSettings;
|
private final PreferenceController mPreferenceController;
|
||||||
private final Consumer<RemoteEvent> mRemoteEventConsumer = this::processRemoteEvent;
|
private final Consumer<RemoteEvent> mRemoteEventConsumer = this::processRemoteEvent;
|
||||||
private final VirtualDisplay mVirtualDisplay;
|
private final VirtualDisplay mVirtualDisplay;
|
||||||
private final VirtualDpad mDpad;
|
private final VirtualDpad mDpad;
|
||||||
@@ -113,19 +113,19 @@ class RemoteDisplay implements AutoCloseable {
|
|||||||
VirtualDevice virtualDevice,
|
VirtualDevice virtualDevice,
|
||||||
RemoteIo remoteIo,
|
RemoteIo remoteIo,
|
||||||
@DisplayType int displayType,
|
@DisplayType int displayType,
|
||||||
Settings settings) {
|
PreferenceController preferenceController) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mRemoteIo = remoteIo;
|
mRemoteIo = remoteIo;
|
||||||
mRemoteDisplayId = event.getDisplayId();
|
mRemoteDisplayId = event.getDisplayId();
|
||||||
mVirtualDevice = virtualDevice;
|
mVirtualDevice = virtualDevice;
|
||||||
mPendingIntentExecutor = context.getMainExecutor();
|
mPendingIntentExecutor = context.getMainExecutor();
|
||||||
mDisplayType = displayType;
|
mDisplayType = displayType;
|
||||||
mSettings = settings;
|
mPreferenceController = preferenceController;
|
||||||
|
|
||||||
setCapabilities(event.getDisplayCapabilities());
|
setCapabilities(event.getDisplayCapabilities());
|
||||||
|
|
||||||
int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
|
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;
|
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
|
||||||
}
|
}
|
||||||
if (mDisplayType == DISPLAY_TYPE_MIRROR) {
|
if (mDisplayType == DISPLAY_TYPE_MIRROR) {
|
||||||
@@ -142,6 +142,9 @@ class RemoteDisplay implements AutoCloseable {
|
|||||||
/* executor= */ Runnable::run,
|
/* executor= */ Runnable::run,
|
||||||
/* callback= */ null);
|
/* callback= */ null);
|
||||||
|
|
||||||
|
mVirtualDevice.setDisplayImePolicy(
|
||||||
|
getDisplayId(), mPreferenceController.getInt(R.string.pref_display_ime_policy));
|
||||||
|
|
||||||
mDpad =
|
mDpad =
|
||||||
virtualDevice.createVirtualDpad(
|
virtualDevice.createVirtualDpad(
|
||||||
new VirtualDpadConfig.Builder()
|
new VirtualDpadConfig.Builder()
|
||||||
@@ -164,9 +167,8 @@ class RemoteDisplay implements AutoCloseable {
|
|||||||
if (mVideoManager != null) {
|
if (mVideoManager != null) {
|
||||||
mVideoManager.stop();
|
mVideoManager.stop();
|
||||||
}
|
}
|
||||||
mVideoManager =
|
mVideoManager = VideoManager.createEncoder(mRemoteDisplayId, mRemoteIo,
|
||||||
VideoManager.createEncoder(
|
mPreferenceController.getBoolean(R.string.pref_record_encoder_output));
|
||||||
mRemoteDisplayId, mRemoteIo, mSettings.recordEncoderOutput);
|
|
||||||
Surface surface = mVideoManager.createInputSurface(mWidth, mHeight, DISPLAY_FPS);
|
Surface surface = mVideoManager.createInputSurface(mWidth, mHeight, DISPLAY_FPS);
|
||||||
mVirtualDisplay.setSurface(surface);
|
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 dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -96,7 +98,7 @@ public final class VdmService extends Hilt_VdmService {
|
|||||||
@Inject ConnectionManager mConnectionManager;
|
@Inject ConnectionManager mConnectionManager;
|
||||||
@Inject RemoteIo mRemoteIo;
|
@Inject RemoteIo mRemoteIo;
|
||||||
@Inject AudioStreamer mAudioStreamer;
|
@Inject AudioStreamer mAudioStreamer;
|
||||||
@Inject Settings mSettings;
|
@Inject PreferenceController mPreferenceController;
|
||||||
@Inject DisplayRepository mDisplayRepository;
|
@Inject DisplayRepository mDisplayRepository;
|
||||||
|
|
||||||
private RemoteSensorManager mRemoteSensorManager = null;
|
private RemoteSensorManager mRemoteSensorManager = null;
|
||||||
@@ -196,14 +198,47 @@ public final class VdmService extends Hilt_VdmService {
|
|||||||
|
|
||||||
mRemoteIo.addMessageConsumer(mRemoteEventConsumer);
|
mRemoteIo.addMessageConsumer(mRemoteEventConsumer);
|
||||||
|
|
||||||
if (mSettings.audioEnabled) {
|
if (mPreferenceController.getBoolean(R.string.pref_enable_client_audio)) {
|
||||||
mAudioStreamer.start();
|
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
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
mPreferenceController.removePreferenceObserver(this);
|
||||||
mConnectionManager.removeConnectionCallback(mConnectionCallback);
|
mConnectionManager.removeConnectionCallback(mConnectionCallback);
|
||||||
closeVirtualDevice();
|
closeVirtualDevice();
|
||||||
mRemoteIo.removeMessageConsumer(mRemoteEventConsumer);
|
mRemoteIo.removeMessageConsumer(mRemoteEventConsumer);
|
||||||
@@ -224,7 +259,7 @@ public final class VdmService extends Hilt_VdmService {
|
|||||||
mVirtualDevice,
|
mVirtualDevice,
|
||||||
mRemoteIo,
|
mRemoteIo,
|
||||||
mPendingDisplayType,
|
mPendingDisplayType,
|
||||||
mSettings);
|
mPreferenceController);
|
||||||
mDisplayRepository.addDisplay(remoteDisplay);
|
mDisplayRepository.addDisplay(remoteDisplay);
|
||||||
mPendingDisplayType = RemoteDisplay.DISPLAY_TYPE_APP;
|
mPendingDisplayType = RemoteDisplay.DISPLAY_TYPE_APP;
|
||||||
if (mPendingRemoteIntent != null) {
|
if (mPendingRemoteIntent != null) {
|
||||||
@@ -241,10 +276,7 @@ public final class VdmService extends Hilt_VdmService {
|
|||||||
CompanionDeviceManager cdm =
|
CompanionDeviceManager cdm =
|
||||||
Objects.requireNonNull(getSystemService(CompanionDeviceManager.class));
|
Objects.requireNonNull(getSystemService(CompanionDeviceManager.class));
|
||||||
RoleManager rm = Objects.requireNonNull(getSystemService(RoleManager.class));
|
RoleManager rm = Objects.requireNonNull(getSystemService(RoleManager.class));
|
||||||
final String deviceProfile =
|
final String deviceProfile = mPreferenceController.getString(R.string.pref_device_profile);
|
||||||
mSettings.deviceStreaming
|
|
||||||
? AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING
|
|
||||||
: AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
|
|
||||||
for (AssociationInfo associationInfo : cdm.getMyAssociations()) {
|
for (AssociationInfo associationInfo : cdm.getMyAssociations()) {
|
||||||
// Flashing the device clears the role and the permissions, but not the CDM
|
// Flashing the device clears the role and the permissions, but not the CDM
|
||||||
// associations.
|
// associations.
|
||||||
@@ -305,24 +337,24 @@ public final class VdmService extends Hilt_VdmService {
|
|||||||
.setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
|
.setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
|
||||||
.setAudioPlaybackSessionId(mAudioStreamer.getPlaybackSessionId());
|
.setAudioPlaybackSessionId(mAudioStreamer.getPlaybackSessionId());
|
||||||
|
|
||||||
if (mSettings.alwaysUnlocked) {
|
if (mPreferenceController.getBoolean(R.string.pref_always_unlocked_device)) {
|
||||||
virtualDeviceBuilder.setLockState(LOCK_STATE_ALWAYS_UNLOCKED);
|
virtualDeviceBuilder.setLockState(LOCK_STATE_ALWAYS_UNLOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mSettings.customHome) {
|
if (mPreferenceController.getBoolean(R.string.pref_enable_custom_home)) {
|
||||||
virtualDeviceBuilder.setHomeComponent(
|
virtualDeviceBuilder.setHomeComponent(
|
||||||
new ComponentName(this, CustomLauncherActivity.class));
|
new ComponentName(this, CustomLauncherActivity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mSettings.includeInRecents) {
|
if (!mPreferenceController.getBoolean(R.string.pref_enable_recents)) {
|
||||||
virtualDeviceBuilder.setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_CUSTOM);
|
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);
|
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()) {
|
for (SensorCapabilities sensor : mDeviceCapabilities.getSensorCapabilitiesList()) {
|
||||||
virtualDeviceBuilder.addVirtualSensorConfig(
|
virtualDeviceBuilder.addVirtualSensorConfig(
|
||||||
new VirtualSensorConfig.Builder(
|
new VirtualSensorConfig.Builder(
|
||||||
@@ -353,7 +385,8 @@ public final class VdmService extends Hilt_VdmService {
|
|||||||
mRemoteSensorManager.setVirtualSensors(mVirtualDevice.getVirtualSensorList());
|
mRemoteSensorManager.setVirtualSensors(mVirtualDevice.getVirtualSensorList());
|
||||||
}
|
}
|
||||||
|
|
||||||
mVirtualDevice.setShowPointerIcon(mSettings.showPointerIcon);
|
mVirtualDevice.setShowPointerIcon(
|
||||||
|
mPreferenceController.getBoolean(R.string.pref_show_pointer_icon));
|
||||||
|
|
||||||
mVirtualDevice.addActivityListener(
|
mVirtualDevice.addActivityListener(
|
||||||
MoreExecutors.directExecutor(),
|
MoreExecutors.directExecutor(),
|
||||||
@@ -447,73 +480,19 @@ public final class VdmService extends Hilt_VdmService {
|
|||||||
.ifPresent(d -> d.launchIntent(pendingIntent));
|
.ifPresent(d -> d.launchIntent(pendingIntent));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDisplayRotationEnabled(boolean enabled) {
|
private void recreateVirtualDevice() {
|
||||||
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) {
|
|
||||||
if (mVirtualDevice != null) {
|
if (mVirtualDevice != null) {
|
||||||
closeVirtualDevice();
|
closeVirtualDevice();
|
||||||
}
|
|
||||||
settingsChange.apply();
|
|
||||||
if (mDeviceCapabilities != null) {
|
if (mDeviceCapabilities != null) {
|
||||||
associateAndCreateVirtualDevice();
|
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",
|
"libenv_logger",
|
||||||
"libglob",
|
"libglob",
|
||||||
"liblog_rust",
|
"liblog_rust",
|
||||||
|
"libnix",
|
||||||
"libonce_cell",
|
"libonce_cell",
|
||||||
"libregex",
|
"libregex",
|
||||||
"libserde",
|
"libserde",
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
fmayle@google.com
|
fmayle@google.com
|
||||||
|
qwandor@google.com
|
||||||
smoreland@google.com
|
smoreland@google.com
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
use super::metadata::WorkspaceMetadata;
|
use super::metadata::WorkspaceMetadata;
|
||||||
use super::{Crate, CrateType, Extern, ExternType};
|
use super::{Crate, CrateType, Extern, ExternType};
|
||||||
|
use crate::CargoOutput;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -23,7 +24,6 @@ use once_cell::sync::Lazy;
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{read_to_string, File};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -31,16 +31,14 @@ use std::path::PathBuf;
|
|||||||
/// the rustc invocations.
|
/// the rustc invocations.
|
||||||
///
|
///
|
||||||
/// Ignores crates outside the current directory and build script crates.
|
/// Ignores crates outside the current directory and build script crates.
|
||||||
pub fn parse_cargo_out(
|
pub fn parse_cargo_out(cargo_output: &CargoOutput) -> Result<Vec<Crate>> {
|
||||||
cargo_out_path: impl AsRef<Path>,
|
let metadata = serde_json::from_str(&cargo_output.cargo_metadata)
|
||||||
cargo_metadata_path: impl AsRef<Path>,
|
.context("failed to parse cargo metadata")?;
|
||||||
) -> Result<Vec<Crate>> {
|
parse_cargo_out_str(
|
||||||
let cargo_out = read_to_string(cargo_out_path).context("failed to read cargo.out")?;
|
&cargo_output.cargo_out,
|
||||||
let metadata = serde_json::from_reader(
|
&metadata,
|
||||||
File::open(cargo_metadata_path).context("failed to open cargo.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
|
/// 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 anyhow::{anyhow, bail, Context, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs::File;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@@ -100,14 +99,9 @@ pub enum TargetKind {
|
|||||||
Test,
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_cargo_metadata_file(
|
pub fn parse_cargo_metadata_str(cargo_metadata: &str, cfg: &VariantConfig) -> Result<Vec<Crate>> {
|
||||||
cargo_metadata_path: impl AsRef<Path>,
|
let metadata =
|
||||||
cfg: &VariantConfig,
|
serde_json::from_str(cargo_metadata).context("failed to parse cargo metadata")?;
|
||||||
) -> 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")?;
|
|
||||||
parse_cargo_metadata(&metadata, &cfg.features, cfg.tests)
|
parse_cargo_metadata(&metadata, &cfg.features, cfg.tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,7 +433,13 @@ mod tests {
|
|||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|variant_cfg| {
|
.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<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(crates, expected_crates);
|
assert_eq!(crates, expected_crates);
|
||||||
|
|||||||
@@ -40,20 +40,23 @@ use anyhow::Context;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bp::*;
|
use bp::*;
|
||||||
use cargo::{
|
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::Parser;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
use nix::unistd::pipe2;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{write, File};
|
use std::fs::{read_to_string, write, File};
|
||||||
use std::io::Write;
|
use std::io::{Read, Write};
|
||||||
|
use std::os::fd::{FromRawFd, OwnedFd};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
// Major TODOs
|
// Major TODOs
|
||||||
// * handle errors, esp. in cargo.out parsing. they should fail the program with an error code
|
// * 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_out_path = "cargo.out";
|
||||||
let cargo_metadata_path = "cargo.metadata";
|
let cargo_metadata_path = "cargo.metadata";
|
||||||
if !args.reuse_cargo_out || !Path::new(cargo_out_path).exists() {
|
let cargo_output = if args.reuse_cargo_out && Path::new(cargo_out_path).exists() {
|
||||||
generate_cargo_out(cfg, cargo_out_path, cargo_metadata_path)
|
CargoOutput {
|
||||||
.context("generate_cargo_out failed")?;
|
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 {
|
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 {
|
} 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_cargo(cargo_out: &mut File, cmd: &mut Command) -> Result<()> {
|
/// Runs the given command, and returns its standard output and standard error as a string.
|
||||||
use std::os::unix::io::OwnedFd;
|
fn run_cargo(cmd: &mut Command) -> Result<String> {
|
||||||
use std::process::Stdio;
|
let (pipe_read, pipe_write) = pipe()?;
|
||||||
let fd: OwnedFd = cargo_out.try_clone()?.into();
|
cmd.stdout(pipe_write.try_clone()?).stderr(pipe_write).stdin(Stdio::null());
|
||||||
debug!("Running: {:?}\n", cmd);
|
debug!("Running: {:?}\n", cmd);
|
||||||
let output = cmd.stdout(Stdio::from(fd.try_clone()?)).stderr(Stdio::from(fd)).output()?;
|
let mut child = cmd.spawn()?;
|
||||||
if !output.status.success() {
|
|
||||||
bail!("cargo command failed with exit status: {:?}", output.status);
|
// 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
|
||||||
Ok(())
|
// 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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run various cargo commands and save the output to `cargo_out_path`.
|
Ok(output)
|
||||||
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 verbose_args = ["-v"];
|
||||||
let target_dir_args = ["--target-dir", "target.tmp"];
|
let target_dir_args = ["--target-dir", "target.tmp"];
|
||||||
|
|
||||||
// cargo clean
|
// 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")?;
|
.context("Running cargo clean")?;
|
||||||
|
|
||||||
let default_target = "x86_64-unknown-linux-gnu";
|
let default_target = "x86_64-unknown-linux-gnu";
|
||||||
@@ -428,8 +460,7 @@ fn generate_cargo_out(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// cargo metadata
|
// cargo metadata
|
||||||
run_cargo(
|
let cargo_metadata = run_cargo(
|
||||||
&mut cargo_metadata_file,
|
|
||||||
Command::new("cargo")
|
Command::new("cargo")
|
||||||
.arg("metadata")
|
.arg("metadata")
|
||||||
.arg("-q") // don't output warnings to stderr
|
.arg("-q") // don't output warnings to stderr
|
||||||
@@ -439,10 +470,10 @@ fn generate_cargo_out(
|
|||||||
)
|
)
|
||||||
.context("Running cargo metadata")?;
|
.context("Running cargo metadata")?;
|
||||||
|
|
||||||
|
let mut cargo_out = String::new();
|
||||||
if cfg.run_cargo {
|
if cfg.run_cargo {
|
||||||
// cargo build
|
// cargo build
|
||||||
run_cargo(
|
cargo_out += &run_cargo(
|
||||||
&mut cargo_out_file,
|
|
||||||
Command::new("cargo")
|
Command::new("cargo")
|
||||||
.args(["build", "--target", default_target])
|
.args(["build", "--target", default_target])
|
||||||
.args(verbose_args)
|
.args(verbose_args)
|
||||||
@@ -453,8 +484,7 @@ fn generate_cargo_out(
|
|||||||
|
|
||||||
if cfg.tests {
|
if cfg.tests {
|
||||||
// cargo build --tests
|
// cargo build --tests
|
||||||
run_cargo(
|
cargo_out += &run_cargo(
|
||||||
&mut cargo_out_file,
|
|
||||||
Command::new("cargo")
|
Command::new("cargo")
|
||||||
.args(["build", "--target", default_target, "--tests"])
|
.args(["build", "--target", default_target, "--tests"])
|
||||||
.args(verbose_args)
|
.args(verbose_args)
|
||||||
@@ -463,8 +493,7 @@ fn generate_cargo_out(
|
|||||||
.args(&feature_args),
|
.args(&feature_args),
|
||||||
)?;
|
)?;
|
||||||
// cargo test -- --list
|
// cargo test -- --list
|
||||||
run_cargo(
|
cargo_out += &run_cargo(
|
||||||
&mut cargo_out_file,
|
|
||||||
Command::new("cargo")
|
Command::new("cargo")
|
||||||
.args(["test", "--target", default_target])
|
.args(["test", "--target", default_target])
|
||||||
.args(target_dir_args)
|
.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`.
|
/// Create the Android.bp file for `package_dir`.
|
||||||
|
|||||||
Reference in New Issue
Block a user