Loading core/api/system-current.txt +47 −0 Original line number Diff line number Diff line Loading @@ -2964,6 +2964,7 @@ package android.companion.virtual { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method public int getDeviceId(); method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String); method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); Loading @@ -2981,6 +2982,7 @@ package android.companion.virtual { method public int getLockState(); method @Nullable public String getName(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1 Loading @@ -2997,6 +2999,7 @@ package android.companion.virtual { public static final class VirtualDeviceParams.Builder { ctor public VirtualDeviceParams.Builder(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig); method @NonNull public android.companion.virtual.VirtualDeviceParams build(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); Loading Loading @@ -3054,6 +3057,50 @@ package android.companion.virtual.audio { } package android.companion.virtual.sensor { public class VirtualSensor { method @NonNull public String getName(); method public int getType(); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent); } public static interface VirtualSensor.SensorStateChangeCallback { method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration); } public final class VirtualSensorConfig implements android.os.Parcelable { method public int describeContents(); method @NonNull public String getName(); method public int getType(); method @Nullable public String getVendor(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR; } public static final class VirtualSensorConfig.Builder { ctor public VirtualSensorConfig.Builder(int, @NonNull String); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build(); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String); } public final class VirtualSensorEvent implements android.os.Parcelable { method public int describeContents(); method public long getTimestampNanos(); method @NonNull public float[] getValues(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR; } public static final class VirtualSensorEvent.Builder { ctor public VirtualSensorEvent.Builder(@NonNull float[]); method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build(); method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long); } } package android.content { public class ApexEnvironment { core/java/android/companion/virtual/IVirtualDevice.aidl +21 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,9 @@ package android.companion.virtual; import android.app.PendingIntent; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.graphics.Point; import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; Loading Loading @@ -96,6 +99,24 @@ interface IVirtualDevice { boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event); boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event); /** * Creates a virtual sensor, capable of injecting sensor events into the system. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config); /** * Removes the sensor corresponding to the given token from the system. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") void unregisterSensor(IBinder token); /** * Sends an event to the virtual sensor corresponding to the given token. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event); /** * Launches a pending intent on the given display that is owned by this virtual device. */ Loading core/java/android/companion/virtual/VirtualDeviceManager.java +71 −0 Original line number Diff line number Diff line Loading @@ -22,12 +22,15 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.PendingIntent; import android.companion.AssociationInfo; import android.companion.virtual.audio.VirtualAudioDevice; import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; Loading Loading @@ -58,6 +61,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.IntConsumer; Loading Loading @@ -89,6 +93,26 @@ public final class VirtualDeviceManager { */ public static final int INVALID_DEVICE_ID = -1; /** * Broadcast Action: A Virtual Device was removed. * * <p class="note">This is a protected intent that can only be sent by the system.</p> * * @hide */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_VIRTUAL_DEVICE_REMOVED = "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED"; /** * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}. * Contains the identifier of the virtual device, which was removed. * * @hide */ public static final String EXTRA_VIRTUAL_DEVICE_ID = "android.companion.virtual.extra.VIRTUAL_DEVICE_ID"; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( Loading Loading @@ -251,7 +275,10 @@ public final class VirtualDeviceManager { }; @Nullable private VirtualAudioDevice mVirtualAudioDevice; @NonNull private List<VirtualSensor> mVirtualSensors = new ArrayList<>(); @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) private VirtualDevice( IVirtualDeviceManager service, Context context, Loading @@ -265,6 +292,10 @@ public final class VirtualDeviceManager { associationId, params, mActivityListenerBinder); final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs(); for (int i = 0; i < virtualSensorConfigs.size(); ++i) { mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i))); } } /** Loading @@ -278,6 +309,23 @@ public final class VirtualDeviceManager { } } /** * Returns this device's sensor with the given type and name, if any. * * @see VirtualDeviceParams.Builder#addVirtualSensorConfig * * @param type The type of the sensor. * @param name The name of the sensor. * @return The matching sensor if found, {@code null} otherwise. */ @Nullable public VirtualSensor getVirtualSensor(int type, @NonNull String name) { return mVirtualSensors.stream() .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name)) .findAny() .orElse(null); } /** * Launches a given pending intent on the give display ID. * Loading Loading @@ -438,6 +486,7 @@ public final class VirtualDeviceManager { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { try { // This also takes care of unregistering all virtual sensors. mVirtualDevice.close(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading Loading @@ -622,6 +671,28 @@ public final class VirtualDeviceManager { } } /** * Creates a virtual sensor, capable of injecting sensor events into the system. Only for * internal use, since device sensors must remain valid for the entire lifetime of the * device. * * @param config The configuration of the sensor. * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) { Objects.requireNonNull(config); try { final IBinder token = new Binder( "android.hardware.sensor.VirtualSensor:" + config.getName()); mVirtualDevice.createVirtualSensor(token, config); return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Adds an activity listener to listen for events such as top activity change or virtual * display task stack became empty. Loading core/java/android/companion/virtual/VirtualDeviceParams.java +76 −27 Original line number Diff line number Diff line Loading @@ -23,20 +23,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.util.Preconditions; 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.Collections; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -158,6 +160,7 @@ public final class VirtualDeviceParams implements Parcelable { @Nullable private final String mName; // Mapping of @PolicyType to @DevicePolicy @NonNull private final SparseIntArray mDevicePolicies; @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs; private VirtualDeviceParams( @LockState int lockState, Loading @@ -169,24 +172,22 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull Set<ComponentName> blockedActivities, @ActivityPolicy int defaultActivityPolicy, @Nullable String name, @NonNull SparseIntArray devicePolicies) { Preconditions.checkNotNull(usersWithMatchingAccounts); Preconditions.checkNotNull(allowedCrossTaskNavigations); Preconditions.checkNotNull(blockedCrossTaskNavigations); Preconditions.checkNotNull(allowedActivities); Preconditions.checkNotNull(blockedActivities); Preconditions.checkNotNull(devicePolicies); @NonNull SparseIntArray devicePolicies, @NonNull List<VirtualSensorConfig> virtualSensorConfigs) { mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations); mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations); mUsersWithMatchingAccounts = new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts)); mAllowedCrossTaskNavigations = new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations)); mBlockedCrossTaskNavigations = new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations)); mDefaultNavigationPolicy = defaultNavigationPolicy; mAllowedActivities = new ArraySet<>(allowedActivities); mBlockedActivities = new ArraySet<>(blockedActivities); mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities)); mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities)); mDefaultActivityPolicy = defaultActivityPolicy; mName = name; mDevicePolicies = devicePolicies; mDevicePolicies = Objects.requireNonNull(devicePolicies); mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs); } @SuppressWarnings("unchecked") Loading @@ -201,6 +202,8 @@ public final class VirtualDeviceParams implements Parcelable { mDefaultActivityPolicy = parcel.readInt(); mName = parcel.readString8(); mDevicePolicies = parcel.readSparseIntArray(); mVirtualSensorConfigs = new ArrayList<>(); parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR); } /** Loading Loading @@ -316,6 +319,15 @@ public final class VirtualDeviceParams implements Parcelable { return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT); } /** * Returns the configurations for all sensors that should be created for this device. * * @see Builder#addVirtualSensorConfig */ public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() { return mVirtualSensorConfigs; } @Override public int describeContents() { return 0; Loading @@ -333,6 +345,7 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeInt(mDefaultActivityPolicy); dest.writeString8(mName); dest.writeSparseIntArray(mDevicePolicies); dest.writeTypedList(mVirtualSensorConfigs); } @Override Loading Loading @@ -428,6 +441,7 @@ public final class VirtualDeviceParams implements Parcelable { private boolean mDefaultActivityPolicyConfigured = false; @Nullable private String mName; @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray(); @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>(); /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} Loading Loading @@ -467,8 +481,7 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setUsersWithMatchingAccounts( @NonNull Set<UserHandle> usersWithMatchingAccounts) { Preconditions.checkNotNull(usersWithMatchingAccounts); mUsersWithMatchingAccounts = usersWithMatchingAccounts; mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts); return this; } Loading @@ -491,7 +504,6 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setAllowedCrossTaskNavigations( @NonNull Set<ComponentName> allowedCrossTaskNavigations) { Preconditions.checkNotNull(allowedCrossTaskNavigations); if (mDefaultNavigationPolicyConfigured && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) { throw new IllegalArgumentException( Loading @@ -500,7 +512,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED; mDefaultNavigationPolicyConfigured = true; mAllowedCrossTaskNavigations = allowedCrossTaskNavigations; mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations); return this; } Loading @@ -523,7 +535,6 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setBlockedCrossTaskNavigations( @NonNull Set<ComponentName> blockedCrossTaskNavigations) { Preconditions.checkNotNull(blockedCrossTaskNavigations); if (mDefaultNavigationPolicyConfigured && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) { throw new IllegalArgumentException( Loading @@ -532,7 +543,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED; mDefaultNavigationPolicyConfigured = true; mBlockedCrossTaskNavigations = blockedCrossTaskNavigations; mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations); return this; } Loading @@ -551,7 +562,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) { Preconditions.checkNotNull(allowedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) { throw new IllegalArgumentException( Loading @@ -559,7 +569,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED; mDefaultActivityPolicyConfigured = true; mAllowedActivities = allowedActivities; mAllowedActivities = Objects.requireNonNull(allowedActivities); return this; } Loading @@ -578,7 +588,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) { Preconditions.checkNotNull(blockedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) { throw new IllegalArgumentException( Loading @@ -586,7 +595,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; mDefaultActivityPolicyConfigured = true; mBlockedActivities = blockedActivities; mBlockedActivities = Objects.requireNonNull(blockedActivities); return this; } Loading Loading @@ -620,11 +629,50 @@ public final class VirtualDeviceParams implements Parcelable { return this; } /** * Adds a configuration for a sensor that should be created for this virtual device. * * Device sensors must remain valid for the entire lifetime of the device, hence they are * created together with the device itself, and removed when the device is removed. * * Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}. * * @see android.companion.virtual.sensor.VirtualSensor * @see #addDevicePolicy */ @NonNull public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) { mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig)); return this; } /** * Builds the {@link VirtualDeviceParams} instance. * * @throws IllegalArgumentException if there's mismatch between policy definition and * the passed parameters or if there are sensor configs with the same type and name. * */ @NonNull public VirtualDeviceParams build() { if (!mVirtualSensorConfigs.isEmpty() && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT) != DEVICE_POLICY_CUSTOM)) { throw new IllegalArgumentException( "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating " + "virtual sensors."); } SparseArray<Set<String>> sensorNameByType = new SparseArray(); for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) { VirtualSensorConfig config = mVirtualSensorConfigs.get(i); Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>()); if (!sensorNames.add(config.getName())) { throw new IllegalArgumentException( "Sensor names must be unique for a particular sensor type."); } sensorNameByType.put(config.getType(), sensorNames); } return new VirtualDeviceParams( mLockState, mUsersWithMatchingAccounts, Loading @@ -635,7 +683,8 @@ public final class VirtualDeviceParams implements Parcelable { mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies); mDevicePolicies, mVirtualSensorConfigs); } } } core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl 0 → 100644 +35 −0 Original line number Diff line number Diff line /* * 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.sensor; /** * Interface for notification of listener registration changes for a virtual sensor. * * @hide */ oneway interface IVirtualSensorStateChangeCallback { /** * Called when the registered listeners to a virtual sensor have changed. * * @param enabled Whether the sensor is enabled. * @param samplingPeriodMicros The requested sensor's sampling period in microseconds. * @param batchReportingLatencyMicros The requested maximum time interval in microseconds * between the delivery of two batches of sensor events. */ void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros); } Loading
core/api/system-current.txt +47 −0 Original line number Diff line number Diff line Loading @@ -2964,6 +2964,7 @@ package android.companion.virtual { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method public int getDeviceId(); method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String); method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); Loading @@ -2981,6 +2982,7 @@ package android.companion.virtual { method public int getLockState(); method @Nullable public String getName(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1 Loading @@ -2997,6 +2999,7 @@ package android.companion.virtual { public static final class VirtualDeviceParams.Builder { ctor public VirtualDeviceParams.Builder(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig); method @NonNull public android.companion.virtual.VirtualDeviceParams build(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); Loading Loading @@ -3054,6 +3057,50 @@ package android.companion.virtual.audio { } package android.companion.virtual.sensor { public class VirtualSensor { method @NonNull public String getName(); method public int getType(); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent); } public static interface VirtualSensor.SensorStateChangeCallback { method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration); } public final class VirtualSensorConfig implements android.os.Parcelable { method public int describeContents(); method @NonNull public String getName(); method public int getType(); method @Nullable public String getVendor(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR; } public static final class VirtualSensorConfig.Builder { ctor public VirtualSensorConfig.Builder(int, @NonNull String); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build(); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String); } public final class VirtualSensorEvent implements android.os.Parcelable { method public int describeContents(); method public long getTimestampNanos(); method @NonNull public float[] getValues(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR; } public static final class VirtualSensorEvent.Builder { ctor public VirtualSensorEvent.Builder(@NonNull float[]); method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build(); method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long); } } package android.content { public class ApexEnvironment {
core/java/android/companion/virtual/IVirtualDevice.aidl +21 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,9 @@ package android.companion.virtual; import android.app.PendingIntent; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.graphics.Point; import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; Loading Loading @@ -96,6 +99,24 @@ interface IVirtualDevice { boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event); boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event); /** * Creates a virtual sensor, capable of injecting sensor events into the system. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config); /** * Removes the sensor corresponding to the given token from the system. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") void unregisterSensor(IBinder token); /** * Sends an event to the virtual sensor corresponding to the given token. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event); /** * Launches a pending intent on the given display that is owned by this virtual device. */ Loading
core/java/android/companion/virtual/VirtualDeviceManager.java +71 −0 Original line number Diff line number Diff line Loading @@ -22,12 +22,15 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.PendingIntent; import android.companion.AssociationInfo; import android.companion.virtual.audio.VirtualAudioDevice; import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; Loading Loading @@ -58,6 +61,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.IntConsumer; Loading Loading @@ -89,6 +93,26 @@ public final class VirtualDeviceManager { */ public static final int INVALID_DEVICE_ID = -1; /** * Broadcast Action: A Virtual Device was removed. * * <p class="note">This is a protected intent that can only be sent by the system.</p> * * @hide */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_VIRTUAL_DEVICE_REMOVED = "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED"; /** * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}. * Contains the identifier of the virtual device, which was removed. * * @hide */ public static final String EXTRA_VIRTUAL_DEVICE_ID = "android.companion.virtual.extra.VIRTUAL_DEVICE_ID"; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( Loading Loading @@ -251,7 +275,10 @@ public final class VirtualDeviceManager { }; @Nullable private VirtualAudioDevice mVirtualAudioDevice; @NonNull private List<VirtualSensor> mVirtualSensors = new ArrayList<>(); @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) private VirtualDevice( IVirtualDeviceManager service, Context context, Loading @@ -265,6 +292,10 @@ public final class VirtualDeviceManager { associationId, params, mActivityListenerBinder); final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs(); for (int i = 0; i < virtualSensorConfigs.size(); ++i) { mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i))); } } /** Loading @@ -278,6 +309,23 @@ public final class VirtualDeviceManager { } } /** * Returns this device's sensor with the given type and name, if any. * * @see VirtualDeviceParams.Builder#addVirtualSensorConfig * * @param type The type of the sensor. * @param name The name of the sensor. * @return The matching sensor if found, {@code null} otherwise. */ @Nullable public VirtualSensor getVirtualSensor(int type, @NonNull String name) { return mVirtualSensors.stream() .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name)) .findAny() .orElse(null); } /** * Launches a given pending intent on the give display ID. * Loading Loading @@ -438,6 +486,7 @@ public final class VirtualDeviceManager { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { try { // This also takes care of unregistering all virtual sensors. mVirtualDevice.close(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading Loading @@ -622,6 +671,28 @@ public final class VirtualDeviceManager { } } /** * Creates a virtual sensor, capable of injecting sensor events into the system. Only for * internal use, since device sensors must remain valid for the entire lifetime of the * device. * * @param config The configuration of the sensor. * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) { Objects.requireNonNull(config); try { final IBinder token = new Binder( "android.hardware.sensor.VirtualSensor:" + config.getName()); mVirtualDevice.createVirtualSensor(token, config); return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Adds an activity listener to listen for events such as top activity change or virtual * display task stack became empty. Loading
core/java/android/companion/virtual/VirtualDeviceParams.java +76 −27 Original line number Diff line number Diff line Loading @@ -23,20 +23,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.util.Preconditions; 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.Collections; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -158,6 +160,7 @@ public final class VirtualDeviceParams implements Parcelable { @Nullable private final String mName; // Mapping of @PolicyType to @DevicePolicy @NonNull private final SparseIntArray mDevicePolicies; @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs; private VirtualDeviceParams( @LockState int lockState, Loading @@ -169,24 +172,22 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull Set<ComponentName> blockedActivities, @ActivityPolicy int defaultActivityPolicy, @Nullable String name, @NonNull SparseIntArray devicePolicies) { Preconditions.checkNotNull(usersWithMatchingAccounts); Preconditions.checkNotNull(allowedCrossTaskNavigations); Preconditions.checkNotNull(blockedCrossTaskNavigations); Preconditions.checkNotNull(allowedActivities); Preconditions.checkNotNull(blockedActivities); Preconditions.checkNotNull(devicePolicies); @NonNull SparseIntArray devicePolicies, @NonNull List<VirtualSensorConfig> virtualSensorConfigs) { mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations); mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations); mUsersWithMatchingAccounts = new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts)); mAllowedCrossTaskNavigations = new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations)); mBlockedCrossTaskNavigations = new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations)); mDefaultNavigationPolicy = defaultNavigationPolicy; mAllowedActivities = new ArraySet<>(allowedActivities); mBlockedActivities = new ArraySet<>(blockedActivities); mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities)); mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities)); mDefaultActivityPolicy = defaultActivityPolicy; mName = name; mDevicePolicies = devicePolicies; mDevicePolicies = Objects.requireNonNull(devicePolicies); mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs); } @SuppressWarnings("unchecked") Loading @@ -201,6 +202,8 @@ public final class VirtualDeviceParams implements Parcelable { mDefaultActivityPolicy = parcel.readInt(); mName = parcel.readString8(); mDevicePolicies = parcel.readSparseIntArray(); mVirtualSensorConfigs = new ArrayList<>(); parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR); } /** Loading Loading @@ -316,6 +319,15 @@ public final class VirtualDeviceParams implements Parcelable { return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT); } /** * Returns the configurations for all sensors that should be created for this device. * * @see Builder#addVirtualSensorConfig */ public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() { return mVirtualSensorConfigs; } @Override public int describeContents() { return 0; Loading @@ -333,6 +345,7 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeInt(mDefaultActivityPolicy); dest.writeString8(mName); dest.writeSparseIntArray(mDevicePolicies); dest.writeTypedList(mVirtualSensorConfigs); } @Override Loading Loading @@ -428,6 +441,7 @@ public final class VirtualDeviceParams implements Parcelable { private boolean mDefaultActivityPolicyConfigured = false; @Nullable private String mName; @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray(); @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>(); /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} Loading Loading @@ -467,8 +481,7 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setUsersWithMatchingAccounts( @NonNull Set<UserHandle> usersWithMatchingAccounts) { Preconditions.checkNotNull(usersWithMatchingAccounts); mUsersWithMatchingAccounts = usersWithMatchingAccounts; mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts); return this; } Loading @@ -491,7 +504,6 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setAllowedCrossTaskNavigations( @NonNull Set<ComponentName> allowedCrossTaskNavigations) { Preconditions.checkNotNull(allowedCrossTaskNavigations); if (mDefaultNavigationPolicyConfigured && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) { throw new IllegalArgumentException( Loading @@ -500,7 +512,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED; mDefaultNavigationPolicyConfigured = true; mAllowedCrossTaskNavigations = allowedCrossTaskNavigations; mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations); return this; } Loading @@ -523,7 +535,6 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setBlockedCrossTaskNavigations( @NonNull Set<ComponentName> blockedCrossTaskNavigations) { Preconditions.checkNotNull(blockedCrossTaskNavigations); if (mDefaultNavigationPolicyConfigured && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) { throw new IllegalArgumentException( Loading @@ -532,7 +543,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED; mDefaultNavigationPolicyConfigured = true; mBlockedCrossTaskNavigations = blockedCrossTaskNavigations; mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations); return this; } Loading @@ -551,7 +562,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) { Preconditions.checkNotNull(allowedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) { throw new IllegalArgumentException( Loading @@ -559,7 +569,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED; mDefaultActivityPolicyConfigured = true; mAllowedActivities = allowedActivities; mAllowedActivities = Objects.requireNonNull(allowedActivities); return this; } Loading @@ -578,7 +588,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) { Preconditions.checkNotNull(blockedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) { throw new IllegalArgumentException( Loading @@ -586,7 +595,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; mDefaultActivityPolicyConfigured = true; mBlockedActivities = blockedActivities; mBlockedActivities = Objects.requireNonNull(blockedActivities); return this; } Loading Loading @@ -620,11 +629,50 @@ public final class VirtualDeviceParams implements Parcelable { return this; } /** * Adds a configuration for a sensor that should be created for this virtual device. * * Device sensors must remain valid for the entire lifetime of the device, hence they are * created together with the device itself, and removed when the device is removed. * * Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}. * * @see android.companion.virtual.sensor.VirtualSensor * @see #addDevicePolicy */ @NonNull public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) { mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig)); return this; } /** * Builds the {@link VirtualDeviceParams} instance. * * @throws IllegalArgumentException if there's mismatch between policy definition and * the passed parameters or if there are sensor configs with the same type and name. * */ @NonNull public VirtualDeviceParams build() { if (!mVirtualSensorConfigs.isEmpty() && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT) != DEVICE_POLICY_CUSTOM)) { throw new IllegalArgumentException( "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating " + "virtual sensors."); } SparseArray<Set<String>> sensorNameByType = new SparseArray(); for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) { VirtualSensorConfig config = mVirtualSensorConfigs.get(i); Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>()); if (!sensorNames.add(config.getName())) { throw new IllegalArgumentException( "Sensor names must be unique for a particular sensor type."); } sensorNameByType.put(config.getType(), sensorNames); } return new VirtualDeviceParams( mLockState, mUsersWithMatchingAccounts, Loading @@ -635,7 +683,8 @@ public final class VirtualDeviceParams implements Parcelable { mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies); mDevicePolicies, mVirtualSensorConfigs); } } }
core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl 0 → 100644 +35 −0 Original line number Diff line number Diff line /* * 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.sensor; /** * Interface for notification of listener registration changes for a virtual sensor. * * @hide */ oneway interface IVirtualSensorStateChangeCallback { /** * Called when the registered listeners to a virtual sensor have changed. * * @param enabled Whether the sensor is enabled. * @param samplingPeriodMicros The requested sensor's sampling period in microseconds. * @param batchReportingLatencyMicros The requested maximum time interval in microseconds * between the delivery of two batches of sensor events. */ void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros); }