From 156763aa02d5b0e25366e669537d91756b72f30e Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Wed, 5 Oct 2022 12:45:56 +0200 Subject: [PATCH 1/3] Add an API to get the details of all virtual devices. Expose minimal information about a virtual device in `VirtualDevice` and allow clients to access it via VDM. Bug: 239152561 Test: atest VirtualDeviceTest, CTS sent separately Change-Id: I7e21009444205925707821692888dad50b58316f --- core/api/current.txt | 12 ++ core/api/system-current.txt | 3 + .../virtual/IVirtualDeviceManager.aidl | 6 + .../companion/virtual/VirtualDevice.aidl | 19 +++ .../companion/virtual/VirtualDevice.java | 119 ++++++++++++++++++ .../virtual/VirtualDeviceManager.java | 19 +++ .../virtual/VirtualDeviceParams.java | 43 ++++++- .../companion/virtual/VirtualDeviceImpl.java | 5 + .../virtual/VirtualDeviceManagerService.java | 14 +++ .../companion/virtual/VirtualDeviceTest.java | 75 +++++++++++ 10 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 core/java/android/companion/virtual/VirtualDevice.aidl create mode 100644 core/java/android/companion/virtual/VirtualDevice.java create mode 100644 services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java diff --git a/core/api/current.txt b/core/api/current.txt index 7a412d8e2a818..0c98ec3466e29 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9069,6 +9069,18 @@ package android.companion { } +package android.companion.virtual { + + public final class VirtualDevice implements android.os.Parcelable { + method public int describeContents(); + method public int getDeviceId(); + method @Nullable public String getName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + +} + package android.content { public abstract class AbstractThreadedSyncAdapter { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index dd85daaf65c27..aa138d7e7bdb1 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2789,6 +2789,7 @@ package android.companion.virtual { public final class VirtualDeviceManager { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams); + method @NonNull public java.util.List getVirtualDevices(); field public static final int DEFAULT_DEVICE_ID = 0; // 0x0 field public static final int INVALID_DEVICE_ID = -1; // 0xffffffff field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2 @@ -2825,6 +2826,7 @@ package android.companion.virtual { method public int getDefaultActivityPolicy(); method public int getDefaultNavigationPolicy(); method public int getLockState(); + method @Nullable public String getName(); method @NonNull public java.util.Set getUsersWithMatchingAccounts(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 @@ -2844,6 +2846,7 @@ package android.companion.virtual { method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set); method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 5418f7e93dd94..82d7534c84d93 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -18,6 +18,7 @@ package android.companion.virtual; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; +import android.companion.virtual.VirtualDevice; import android.companion.virtual.VirtualDeviceParams; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; @@ -45,6 +46,11 @@ interface IVirtualDeviceManager { in IBinder token, String packageName, int associationId, in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener); + /** + * Returns the details of all available virtual devices. + */ + List getVirtualDevices(); + /** * Creates a virtual display owned by a particular virtual device. * diff --git a/core/java/android/companion/virtual/VirtualDevice.aidl b/core/java/android/companion/virtual/VirtualDevice.aidl new file mode 100644 index 0000000000000..4bbe05f585e8a --- /dev/null +++ b/core/java/android/companion/virtual/VirtualDevice.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual; + +parcelable VirtualDevice; diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java new file mode 100644 index 0000000000000..9e95d472f48bd --- /dev/null +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Details of a particular virtual device. + */ +public final class VirtualDevice implements Parcelable { + + private final int mId; + private final @Nullable String mName; + + /** + * Creates a new instance of {@link VirtualDevice}. + * Only to be used by the VirtualDeviceManagerService. + * + * @hide + */ + public VirtualDevice(int id, @Nullable String name) { + if (id <= VirtualDeviceManager.DEFAULT_DEVICE_ID) { + throw new IllegalArgumentException("VirtualDevice ID mist be greater than " + + VirtualDeviceManager.DEFAULT_DEVICE_ID); + } + mId = id; + mName = name; + } + + private VirtualDevice(@NonNull Parcel parcel) { + mId = parcel.readInt(); + mName = parcel.readString8(); + } + + /** + * Returns the unique ID of the virtual device. + */ + public int getDeviceId() { + return mId; + } + + /** + * Returns the name of the virtual device (optionally) provided during its creation. + * + * @see VirtualDeviceParams.Builder#setName(String) + */ + public @Nullable String getName() { + return mName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeString8(mName); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VirtualDevice)) { + return false; + } + VirtualDevice that = (VirtualDevice) o; + return mId == that.mId + && Objects.equals(mName, that.mName); + } + + @Override + public int hashCode() { + return Objects.hash(mId, mName); + } + + @Override + @NonNull + public String toString() { + return "VirtualDevice(" + + " mId=" + mId + + " mName=" + mName + + ")"; + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public VirtualDevice createFromParcel(Parcel in) { + return new VirtualDevice(in); + } + + public VirtualDevice[] newArray(int size) { + return new VirtualDevice[size]; + } + }; +} diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 08bee2552a1f0..7ef92e3edb0d1 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -49,12 +49,15 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.ArrayMap; +import android.util.Log; import android.view.Surface; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; import java.util.function.IntConsumer; @@ -153,6 +156,22 @@ public final class VirtualDeviceManager { } } + /** + * Returns the details of all available virtual devices. + */ + @NonNull + public List getVirtualDevices() { + if (mService == null) { + Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); + return new ArrayList<>(); + } + try { + return mService.getVirtualDevices(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * A virtual device has its own virtual display, audio output, microphone, and camera etc. The * creator of a virtual device can take the output from the virtual display and stream it over diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 3b1ff3fa65b5a..d40c9d63039dd 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.ComponentName; @@ -112,6 +113,7 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull private final ArraySet mBlockedActivities; @ActivityPolicy private final int mDefaultActivityPolicy; + @Nullable private final String mName; private VirtualDeviceParams( @LockState int lockState, @@ -121,7 +123,8 @@ public final class VirtualDeviceParams implements Parcelable { @NavigationPolicy int defaultNavigationPolicy, @NonNull Set allowedActivities, @NonNull Set blockedActivities, - @ActivityPolicy int defaultActivityPolicy) { + @ActivityPolicy int defaultActivityPolicy, + @Nullable String name) { Preconditions.checkNotNull(usersWithMatchingAccounts); Preconditions.checkNotNull(allowedCrossTaskNavigations); Preconditions.checkNotNull(blockedCrossTaskNavigations); @@ -136,6 +139,7 @@ public final class VirtualDeviceParams implements Parcelable { mAllowedActivities = new ArraySet<>(allowedActivities); mBlockedActivities = new ArraySet<>(blockedActivities); mDefaultActivityPolicy = defaultActivityPolicy; + mName = name; } @SuppressWarnings("unchecked") @@ -148,6 +152,7 @@ public final class VirtualDeviceParams implements Parcelable { mAllowedActivities = (ArraySet) parcel.readArraySet(null); mBlockedActivities = (ArraySet) parcel.readArraySet(null); mDefaultActivityPolicy = parcel.readInt(); + mName = parcel.readString8(); } /** @@ -243,6 +248,16 @@ public final class VirtualDeviceParams implements Parcelable { return mDefaultActivityPolicy; } + /** + * Returns the (optional) name of the virtual device. + * + * @see Builder#setName + */ + @Nullable + public String getName() { + return mName; + } + @Override public int describeContents() { return 0; @@ -258,6 +273,7 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeArraySet(mAllowedActivities); dest.writeArraySet(mBlockedActivities); dest.writeInt(mDefaultActivityPolicy); + dest.writeString8(mName); } @Override @@ -276,7 +292,8 @@ public final class VirtualDeviceParams implements Parcelable { && mDefaultNavigationPolicy == that.mDefaultNavigationPolicy && Objects.equals(mAllowedActivities, that.mAllowedActivities) && Objects.equals(mBlockedActivities, that.mBlockedActivities) - && mDefaultActivityPolicy == that.mDefaultActivityPolicy; + && mDefaultActivityPolicy == that.mDefaultActivityPolicy + && Objects.equals(mName, that.mName); } @Override @@ -284,7 +301,7 @@ public final class VirtualDeviceParams implements Parcelable { return Objects.hash( mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations, mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities, - mBlockedActivities, mDefaultActivityPolicy); + mBlockedActivities, mDefaultActivityPolicy, mName); } @Override @@ -299,6 +316,7 @@ public final class VirtualDeviceParams implements Parcelable { + " mAllowedActivities=" + mAllowedActivities + " mBlockedActivities=" + mBlockedActivities + " mDefaultActivityPolicy=" + mDefaultActivityPolicy + + " mName=" + mName + ")"; } @@ -331,6 +349,7 @@ public final class VirtualDeviceParams implements Parcelable { @ActivityPolicy private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; private boolean mDefaultActivityPolicyConfigured = false; + @Nullable private String mName; /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} @@ -493,6 +512,21 @@ public final class VirtualDeviceParams implements Parcelable { return this; } + /** + * Sets the optional virtual device name. + * + *

This string is not typically intended to be displayed to end users, but rather for + * debugging and other developer-facing purposes. + * + *

3rd party applications may be able to see the name (i.e. it's not private to the + * device owner) + */ + @NonNull + public Builder setName(@NonNull String name) { + mName = name; + return this; + } + /** * Builds the {@link VirtualDeviceParams} instance. */ @@ -506,7 +540,8 @@ public final class VirtualDeviceParams implements Parcelable { mDefaultNavigationPolicy, mAllowedActivities, mBlockedActivities, - mDefaultActivityPolicy); + mDefaultActivityPolicy, + mName); } } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 5f27f598ee4ea..2835b69b30397 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -222,6 +222,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mAssociationInfo.getDisplayName(); } + /** Returns the optional name of the device. */ + String getDeviceName() { + return mParams.getName(); + } + /** Returns the unique device ID of this device. */ @Override // Binder call public int getDeviceId() { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 06dfeabfe8323..c400a74da4ced 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -28,6 +28,7 @@ import android.companion.CompanionDeviceManager.OnAssociationsChangedListener; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceManager; +import android.companion.virtual.VirtualDevice; import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; @@ -344,6 +345,19 @@ public class VirtualDeviceManagerService extends SystemService { return displayId; } + @Override // Binder call + public List getVirtualDevices() { + List virtualDevices = new ArrayList<>(); + synchronized (mVirtualDeviceManagerLock) { + for (int i = 0; i < mVirtualDevices.size(); i++) { + final VirtualDeviceImpl device = mVirtualDevices.valueAt(i); + virtualDevices.add( + new VirtualDevice(device.getDeviceId(), device.getDeviceName())); + } + } + return virtualDevices; + } + @Nullable private AssociationInfo getAssociationInfo(String packageName, int associationId) { final int callingUserId = getCallingUserHandle().getIdentifier(); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java new file mode 100644 index 0000000000000..f6f13392ce3a7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022 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.android.server.companion.virtual; + +import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID; +import static android.companion.virtual.VirtualDeviceManager.INVALID_DEVICE_ID; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import android.companion.virtual.VirtualDevice; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualDeviceTest { + + private static final int VIRTUAL_DEVICE_ID = 42; + private static final String VIRTUAL_DEVICE_NAME = "VirtualDeviceName"; + + @Test + public void build_invalidId_shouldThrowIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> new VirtualDevice(INVALID_DEVICE_ID, VIRTUAL_DEVICE_NAME)); + } + + @Test + public void build_defaultId_shouldThrowIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> new VirtualDevice(DEFAULT_DEVICE_ID, VIRTUAL_DEVICE_NAME)); + } + + @Test + public void build_nameIsOptional() { + VirtualDevice virtualDevice = + new VirtualDevice(VIRTUAL_DEVICE_ID, /* name= */ null); + assertThat(virtualDevice.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID); + assertThat(virtualDevice.getName()).isNull(); + } + + @Test + public void parcelable_shouldRecreateSuccessfully() { + VirtualDevice originalDevice = + new VirtualDevice(VIRTUAL_DEVICE_ID, VIRTUAL_DEVICE_NAME); + Parcel parcel = Parcel.obtain(); + originalDevice.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + VirtualDevice device = VirtualDevice.CREATOR.createFromParcel(parcel); + assertThat(device).isEqualTo(originalDevice); + assertThat(device.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID); + assertThat(device.getName()).isEqualTo(VIRTUAL_DEVICE_NAME); + } +} From 0050a51961000057f7858bb2b8892892aaad3232 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Wed, 5 Oct 2022 13:31:28 +0200 Subject: [PATCH 2/3] Open the visibility of VirtualDeviceManager and VirtualDeviceManager#getVirtualDevices. Bug: 239152561 Test: n/a Change-Id: Ib504a3bad36072730a527b139de945874ec4741a --- core/api/current.txt | 7 +++++++ core/api/system-current.txt | 3 --- .../virtual/VirtualDeviceManager.java | 21 ++++++++++++++++--- core/java/android/content/Context.java | 4 ++-- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 0c98ec3466e29..8138f6abea870 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9079,6 +9079,12 @@ package android.companion.virtual { field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + public final class VirtualDeviceManager { + method @NonNull public java.util.List getVirtualDevices(); + field public static final int DEFAULT_DEVICE_ID = 0; // 0x0 + field public static final int INVALID_DEVICE_ID = -1; // 0xffffffff + } + } package android.content { @@ -9903,6 +9909,7 @@ package android.content { field public static final String USER_SERVICE = "user"; field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager"; field @Deprecated public static final String VIBRATOR_SERVICE = "vibrator"; + field public static final String VIRTUAL_DEVICE_SERVICE = "virtualdevice"; field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management"; field @UiContext public static final String WALLPAPER_SERVICE = "wallpaper"; field public static final String WIFI_AWARE_SERVICE = "wifiaware"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index aa138d7e7bdb1..37ba3a5fd36d7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2789,9 +2789,6 @@ package android.companion.virtual { public final class VirtualDeviceManager { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams); - method @NonNull public java.util.List getVirtualDevices(); - field public static final int DEFAULT_DEVICE_ID = 0; // 0x0 - field public static final int INVALID_DEVICE_ID = -1; // 0xffffffff field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2 field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1 field public static final int LAUNCH_SUCCESS = 0; // 0x0 diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 7ef92e3edb0d1..0bb86fbf00f89 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -63,10 +63,7 @@ import java.util.function.IntConsumer; /** * System level service for managing virtual devices. - * - * @hide */ -@SystemApi @SystemService(Context.VIRTUAL_DEVICE_SERVICE) public final class VirtualDeviceManager { @@ -105,19 +102,28 @@ public final class VirtualDeviceManager { /** * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch was * successful. + * + * @hide */ + @SystemApi public static final int LAUNCH_SUCCESS = 0; /** * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch failed * because the pending intent was canceled. + * + * @hide */ + @SystemApi public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; /** * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch failed * because no activity starts were detected as a result of calling the pending intent. + * + * @hide */ + @SystemApi public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; private final IVirtualDeviceManager mService; @@ -143,7 +149,10 @@ public final class VirtualDeviceManager { * @param params The parameters for creating virtual devices. See {@link VirtualDeviceParams} * for the available options. * @return The created virtual device. + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualDevice createVirtualDevice( @@ -178,7 +187,10 @@ public final class VirtualDeviceManager { * to another device, and inject input events that are received from the remote device. * * TODO(b/204081582): Consider using a builder pattern for the input APIs. + * + * @hide */ + @SystemApi public static class VirtualDevice implements AutoCloseable { private final Context mContext; @@ -556,7 +568,10 @@ public final class VirtualDeviceManager { /** * Listener for activity changes in this virtual device. + * + * @hide */ + @SystemApi public interface ActivityListener { /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index cb5a99fcc811e..e28df09e61d78 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3919,7 +3919,7 @@ public abstract class Context { //@hide: INCIDENT_COMPANION_SERVICE, //@hide: STATS_COMPANION_SERVICE, COMPANION_DEVICE_SERVICE, - //@hide: VIRTUAL_DEVICE_SERVICE, + VIRTUAL_DEVICE_SERVICE, CROSS_PROFILE_APPS_SERVICE, //@hide: SYSTEM_UPDATE_SERVICE, //@hide: TIME_DETECTOR_SERVICE, @@ -5441,8 +5441,8 @@ public abstract class Context { * * @see #getSystemService(String) * @see android.companion.virtual.VirtualDeviceManager - * @hide */ + @SuppressLint("ServiceName") public static final String VIRTUAL_DEVICE_SERVICE = "virtualdevice"; /** From 7c1be3298d99cde28ac39b3095391065d619eaa8 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Wed, 5 Oct 2022 14:09:28 +0200 Subject: [PATCH 3/3] Add deviceId to Context and allow for creating Contexts with different deviceId. Applications may create contexts associated with the default host device or with any virtual device. The only way to create a non-default device context for now is via an explcit createDeviceContext call. Bug: 239152561 Test: atest FrameworksCoreTests:ContextTest Change-Id: If18dc7661fb232b0bc40121c723673ba880be9d5 --- core/api/current.txt | 2 + core/java/android/app/ContextImpl.java | 32 +++++++++++++++ core/java/android/content/Context.java | 39 +++++++++++++++++++ core/java/android/content/ContextWrapper.java | 10 +++++ .../src/android/content/ContextTest.java | 25 ++++++++++++ .../src/android/test/mock/MockContext.java | 10 +++++ 6 files changed, 118 insertions(+) diff --git a/core/api/current.txt b/core/api/current.txt index 8138f6abea870..1d679ddafdf25 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9684,6 +9684,7 @@ package android.content { method public abstract android.content.Context createConfigurationContext(@NonNull android.content.res.Configuration); method @NonNull public android.content.Context createContext(@NonNull android.content.ContextParams); method public abstract android.content.Context createContextForSplit(String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public android.content.Context createDeviceContext(int); method public abstract android.content.Context createDeviceProtectedStorageContext(); method @DisplayContext public abstract android.content.Context createDisplayContext(@NonNull android.view.Display); method public abstract android.content.Context createPackageContext(String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -9714,6 +9715,7 @@ package android.content { method public abstract android.content.ContentResolver getContentResolver(); method public abstract java.io.File getDataDir(); method public abstract java.io.File getDatabasePath(String); + method public int getDeviceId(); method public abstract java.io.File getDir(String, int); method @Nullable public android.view.Display getDisplay(); method @Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 0e1b47f655614..10cdf5315b556 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -25,6 +25,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; +import android.companion.virtual.VirtualDevice; +import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; import android.content.AutofillOptions; @@ -241,6 +243,7 @@ class ContextImpl extends Context { @UnsupportedAppUsage private @NonNull Resources mResources; private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet. + private int mDeviceId = VirtualDeviceManager.DEFAULT_DEVICE_ID; /** * If set to {@code true} the resources for this context will be configured for mDisplay which @@ -2700,6 +2703,30 @@ class ContextImpl extends Context { return context; } + @Override + public @NonNull Context createDeviceContext(int deviceId) { + boolean validDeviceId = deviceId == VirtualDeviceManager.DEFAULT_DEVICE_ID; + if (deviceId > VirtualDeviceManager.DEFAULT_DEVICE_ID) { + VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class); + if (vdm != null) { + List virtualDevices = vdm.getVirtualDevices(); + validDeviceId = virtualDevices.stream().anyMatch(d -> d.getDeviceId() == deviceId); + } + } + if (!validDeviceId) { + throw new IllegalArgumentException( + "Not a valid ID of the default device or any virtual device: " + deviceId); + } + + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), + mSplitName, mToken, mUser, mFlags, mClassLoader, null); + + context.mDeviceId = deviceId; + return context; + } + @NonNull @Override public WindowContext createWindowContext(@WindowType int type, @@ -2946,6 +2973,11 @@ class ContextImpl extends Context { } } + @Override + public int getDeviceId() { + return mDeviceId; + } + @Override public DisplayAdjustments getDisplayAdjustments(int displayId) { return mResources.getDisplayAdjustments(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e28df09e61d78..02cf3e68b20ba 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -46,6 +46,7 @@ import android.app.VrManager; import android.app.ambientcontext.AmbientContextManager; import android.app.people.PeopleManager; import android.app.time.TimeManager; +import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; @@ -6828,6 +6829,30 @@ public abstract class Context { @DisplayContext public abstract Context createDisplayContext(@NonNull Display display); + /** + * Returns a new {@code Context} object from the current context but with device association + * given by the {@code deviceId}. Each call to this method returns a new instance of a context + * object. Context objects are not shared; however, common state (such as the + * {@link ClassLoader} and other resources for the same configuration) can be shared, so the + * {@code Context} itself is lightweight. + *

+ * Applications that run on virtual devices may use this method to access the default device + * capabilities and functionality (by passing + * {@link android.companion.virtual.VirtualDeviceManager#DEFAULT_DEVICE_ID}. Similarly, + * applications running on the default device may access the functionality of virtual devices. + *

+ * @param deviceId The ID of the device to associate with this context. + * @return A context associated with the given device ID. + * + * @see #getDeviceId() + * @see VirtualDeviceManager#getVirtualDevices() + * @throws IllegalArgumentException if the given device ID is not a valid ID of the default + * device or a virtual device. + */ + public @NonNull Context createDeviceContext(int deviceId) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** * Creates a Context for a non-activity window. * @@ -7150,6 +7175,20 @@ public abstract class Context { @SuppressWarnings("HiddenAbstractMethod") public abstract void updateDisplay(int displayId); + /** + * Get the device ID this context is associated with. Applications can use this method to + * determine whether they are running on a virtual device and identify that device. + * + * The device ID of the host device is + * {@link android.companion.virtual.VirtualDeviceManager#DEFAULT_DEVICE_ID} + * + * @return the ID of the device this context is associated with. + * @see #createDeviceContext(int) + */ + public int getDeviceId() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** * Indicates whether this Context is restricted. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index e6549187e5c56..a1646a172521f 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1098,6 +1098,11 @@ public class ContextWrapper extends Context { return mBase.createDisplayContext(display); } + @Override + public @NonNull Context createDeviceContext(int deviceId) { + return mBase.createDeviceContext(deviceId); + } + @Override @NonNull public Context createWindowContext(@WindowType int type, @Nullable Bundle options) { @@ -1166,6 +1171,11 @@ public class ContextWrapper extends Context { mBase.updateDisplay(displayId); } + @Override + public int getDeviceId() { + return mBase.getDeviceId(); + } + @Override public Context createDeviceProtectedStorageContext() { return mBase.createDeviceProtectedStorageContext(); diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java index e4a9ce59911b0..bc356f80dca15 100644 --- a/core/tests/coretests/src/android/content/ContextTest.java +++ b/core/tests/coretests/src/android/content/ContextTest.java @@ -16,6 +16,7 @@ package android.content; +import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.view.Display.DEFAULT_DISPLAY; @@ -210,6 +211,30 @@ public class ContextTest { assertFalse(context.isUiContext()); } + @Test + public void testDeviceIdForSystemContext() { + final Context systemContext = + ActivityThread.currentActivityThread().getSystemContext(); + + assertEquals(systemContext.getDeviceId(), DEFAULT_DEVICE_ID); + } + + @Test + public void testDeviceIdForSystemUiContext() { + final Context systemUiContext = + ActivityThread.currentActivityThread().getSystemUiContext(); + + assertEquals(systemUiContext.getDeviceId(), DEFAULT_DEVICE_ID); + } + + @Test + public void testDeviceIdForTestContext() { + final Context testContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals(testContext.getDeviceId(), DEFAULT_DEVICE_ID); + } + private Context createUiContext() { final Context appContext = ApplicationProvider.getApplicationContext(); final DisplayManager displayManager = appContext.getSystemService(DisplayManager.class); diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index 49daad324bd11..8fc8c7d162f49 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -838,6 +838,11 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } + @Override + public @NonNull Context createDeviceContext(int deviceId) { + throw new UnsupportedOperationException(); + } + @Override public @NonNull Context createWindowContext(int type, Bundle options) { throw new UnsupportedOperationException(); @@ -882,6 +887,11 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } + @Override + public int getDeviceId() { + throw new UnsupportedOperationException(); + } + @Override public File[] getExternalFilesDirs(String type) { throw new UnsupportedOperationException();