diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f92c36d488ad2..20d0d6dc79abf 100755 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -95,6 +95,7 @@ package android { field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"; field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN"; field public static final String CREATE_USERS = "android.permission.CREATE_USERS"; + field public static final String CREATE_VIRTUAL_DEVICE = "android.permission.CREATE_VIRTUAL_DEVICE"; field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER"; field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"; @@ -2333,6 +2334,17 @@ package android.companion { } +package android.companion.virtual { + + public final class VirtualDeviceManager { + } + + public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable { + method public void close(); + } + +} + package android.content { public class ApexEnvironment { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 8ea1f8315b89f..37228a581a8e8 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -54,6 +54,8 @@ import android.appwidget.AppWidgetManager; import android.bluetooth.BluetoothManager; import android.companion.CompanionDeviceManager; import android.companion.ICompanionDeviceManager; +import android.companion.virtual.IVirtualDeviceManager; +import android.companion.virtual.VirtualDeviceManager; import android.content.ClipboardManager; import android.content.ContentCaptureOptions; import android.content.Context; @@ -875,6 +877,16 @@ public final class SystemServiceRegistry { return new CompanionDeviceManager(service, ctx.getOuterContext()); }}); + registerService(Context.VIRTUAL_DEVICE_SERVICE, VirtualDeviceManager.class, + new CachedServiceFetcher() { + @Override + public VirtualDeviceManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IVirtualDeviceManager service = IVirtualDeviceManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.VIRTUAL_DEVICE_SERVICE)); + return new VirtualDeviceManager(service, ctx.getOuterContext()); + }}); + registerService(Context.CONSUMER_IR_SERVICE, ConsumerIrManager.class, new CachedServiceFetcher() { @Override diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl new file mode 100644 index 0000000000000..0aa442bfc5035 --- /dev/null +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 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; + +/** + * Interface for a virtual device. + * + * @hide + */ +interface IVirtualDevice { + void close(); +} diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl new file mode 100644 index 0000000000000..91e717d719a99 --- /dev/null +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 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.companion.virtual.IVirtualDevice; + +/** + * Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService. + * + * @hide + */ +interface IVirtualDeviceManager { + + IVirtualDevice createVirtualDevice(); +} diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java new file mode 100644 index 0000000000000..6187de57a2169 --- /dev/null +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 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.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; + +/** + * System level service for managing virtual devices. + * + * @hide + */ +@SystemApi +@SystemService(Context.VIRTUAL_DEVICE_SERVICE) +public final class VirtualDeviceManager { + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "VirtualDeviceManager"; + + private final IVirtualDeviceManager mService; + private final Context mContext; + + /** @hide */ + public VirtualDeviceManager( + @Nullable IVirtualDeviceManager service, @NonNull Context context) { + mService = service; + mContext = context; + } + + /** + * Creates a virtual device. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @Nullable + public VirtualDevice createVirtualDevice() { + // TODO(b/194949534): Add CDM association ID here and unhide this API + try { + IVirtualDevice virtualDevice = mService.createVirtualDevice(); + return new VirtualDevice(mContext, virtualDevice); + } 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. + */ + public static class VirtualDevice implements AutoCloseable { + + private final Context mContext; + private final IVirtualDevice mVirtualDevice; + + private VirtualDevice(Context context, IVirtualDevice virtualDevice) { + mContext = context.getApplicationContext(); + mVirtualDevice = virtualDevice; + } + + /** + * Closes the virtual device, stopping and tearing down any virtual displays, + * audio policies, and event injection that's currently in progress. + */ + public void close() { + try { + mVirtualDevice.close(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index cfd34174f9e33..bfc43334d7c19 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3799,6 +3799,7 @@ public abstract class Context { //@hide: INCIDENT_COMPANION_SERVICE, //@hide: STATS_COMPANION_SERVICE, COMPANION_DEVICE_SERVICE, + //@hide: VIRTUAL_DEVICE_SERVICE, CROSS_PROFILE_APPS_SERVICE, //@hide: SYSTEM_UPDATE_SERVICE, //@hide: TIME_DETECTOR_SERVICE, @@ -5260,6 +5261,16 @@ public abstract class Context { */ public static final String COMPANION_DEVICE_SERVICE = "companiondevice"; + /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.companion.virtual.VirtualDeviceManager} for managing virtual devices. + * + * @see #getSystemService(String) + * @see android.companion.virtual.VirtualDeviceManager + * @hide + */ + public static final String VIRTUAL_DEVICE_SERVICE = "virtualdevice"; + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.hardware.ConsumerIrManager} for transmitting infrared diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 329f02a74cb02..b3a16d97b27b9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5911,6 +5911,11 @@ + + + diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java new file mode 100644 index 0000000000000..18cf6f874ab14 --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.IVirtualDeviceManager; +import android.content.Context; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.ExceptionUtils; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.DumpUtils; +import com.android.server.SystemService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + + +/** @hide */ +@SuppressLint("LongLogTag") +public class VirtualDeviceManagerService extends SystemService { + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "VirtualDeviceManagerService"; + private final VirtualDeviceManagerImpl mImpl; + @GuardedBy("mVirtualDevices") + private final ArrayList mVirtualDevices = new ArrayList<>(); + + public VirtualDeviceManagerService(Context context) { + super(context); + mImpl = new VirtualDeviceManagerImpl(); + } + + @Override + public void onStart() { + publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); + } + + private class VirtualDeviceImpl extends IVirtualDevice.Stub { + + private VirtualDeviceImpl() {} + + @Override + public void close() { + synchronized (mVirtualDevices) { + mVirtualDevices.remove(this); + } + } + } + + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub { + + @Override + public IVirtualDevice createVirtualDevice() { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "createVirtualDevice"); + VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(); + synchronized (mVirtualDevices) { + mVirtualDevices.add(virtualDevice); + } + return virtualDevice; + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (Throwable e) { + Slog.e(LOG_TAG, "Error during IPC", e); + throw ExceptionUtils.propagate(e, RemoteException.class); + } + } + + @Override + public void dump(@NonNull FileDescriptor fd, + @NonNull PrintWriter fout, + @Nullable String[] args) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) { + return; + } + fout.println("Created virtual devices: "); + synchronized (mVirtualDevices) { + for (VirtualDeviceImpl virtualDevice : mVirtualDevices) { + fout.println(virtualDevice.toString()); + } + } + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f351a8c8922fb..ce03b59bc3f52 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -251,6 +251,8 @@ public final class SystemServer implements Dumpable { "com.android.server.print.PrintManagerService"; private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS = "com.android.server.companion.CompanionDeviceManagerService"; + private static final String VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS = + "com.android.server.companion.virtual.VirtualDeviceManagerService"; private static final String STATS_COMPANION_APEX_PATH = "/apex/com.android.os.statsd/javalib/service-statsd.jar"; private static final String SCHEDULING_APEX_PATH = @@ -2322,6 +2324,11 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartCompanionDeviceManager"); mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS); t.traceEnd(); + + // VirtualDeviceManager depends on CDM to control the associations. + t.traceBegin("StartVirtualDeviceManager"); + mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS); + t.traceEnd(); } t.traceBegin("StartRestrictionManager");