diff --git a/core/api/current.txt b/core/api/current.txt index e5cc625038ddf..559f8f551a035 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9070,6 +9070,24 @@ 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; + } + + 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 { public abstract class AbstractThreadedSyncAdapter { @@ -9667,6 +9685,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; @@ -9697,6 +9716,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); @@ -9892,6 +9912,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 752b524378a96..7c1c8ba7410c1 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2798,8 +2798,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); - 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 @@ -2834,6 +2832,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 @@ -2853,6 +2852,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/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/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..0bb86fbf00f89 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -49,21 +49,21 @@ 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; /** * System level service for managing virtual devices. - * - * @hide */ -@SystemApi @SystemService(Context.VIRTUAL_DEVICE_SERVICE) public final class VirtualDeviceManager { @@ -102,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; @@ -140,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( @@ -153,13 +165,32 @@ 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 * 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; @@ -537,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/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/core/java/android/content/Context.java b/core/java/android/content/Context.java index 7b352133e16b9..753c93612f405 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; @@ -3920,7 +3921,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, @@ -5442,8 +5443,8 @@ public abstract class Context { * * @see #getSystemService(String) * @see android.companion.virtual.VirtualDeviceManager - * @hide */ + @SuppressLint("ServiceName") public static final String VIRTUAL_DEVICE_SERVICE = "virtualdevice"; /** @@ -6829,6 +6830,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. * @@ -7151,6 +7176,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/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); + } +} 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();