1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.companion.virtual; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.UserIdInt; 23 import android.app.PendingIntent; 24 import android.companion.virtual.audio.VirtualAudioDevice; 25 import android.companion.virtual.sensor.VirtualSensor; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.hardware.display.DisplayManagerGlobal; 31 import android.hardware.display.IVirtualDisplayCallback; 32 import android.hardware.display.VirtualDisplay; 33 import android.hardware.display.VirtualDisplayConfig; 34 import android.hardware.input.VirtualDpad; 35 import android.hardware.input.VirtualDpadConfig; 36 import android.hardware.input.VirtualKeyboard; 37 import android.hardware.input.VirtualKeyboardConfig; 38 import android.hardware.input.VirtualMouse; 39 import android.hardware.input.VirtualMouseConfig; 40 import android.hardware.input.VirtualNavigationTouchpad; 41 import android.hardware.input.VirtualNavigationTouchpadConfig; 42 import android.hardware.input.VirtualTouchscreen; 43 import android.hardware.input.VirtualTouchscreenConfig; 44 import android.media.AudioManager; 45 import android.os.Binder; 46 import android.os.Bundle; 47 import android.os.Handler; 48 import android.os.IBinder; 49 import android.os.Looper; 50 import android.os.RemoteException; 51 import android.os.ResultReceiver; 52 import android.util.ArrayMap; 53 54 import com.android.internal.annotations.GuardedBy; 55 56 import java.util.List; 57 import java.util.Objects; 58 import java.util.concurrent.Executor; 59 import java.util.function.IntConsumer; 60 61 /** 62 * An internal representation of a virtual device. 63 * 64 * @hide 65 */ 66 public class VirtualDeviceInternal { 67 68 private final Context mContext; 69 private final IVirtualDeviceManager mService; 70 private final IVirtualDevice mVirtualDevice; 71 private final Object mActivityListenersLock = new Object(); 72 @GuardedBy("mActivityListenersLock") 73 private final ArrayMap<VirtualDeviceManager.ActivityListener, ActivityListenerDelegate> 74 mActivityListeners = 75 new ArrayMap<>(); 76 private final Object mIntentInterceptorListenersLock = new Object(); 77 @GuardedBy("mIntentInterceptorListenersLock") 78 private final ArrayMap<VirtualDeviceManager.IntentInterceptorCallback, 79 IntentInterceptorDelegate> mIntentInterceptorListeners = 80 new ArrayMap<>(); 81 private final Object mSoundEffectListenersLock = new Object(); 82 @GuardedBy("mSoundEffectListenersLock") 83 private final ArrayMap<VirtualDeviceManager.SoundEffectListener, SoundEffectListenerDelegate> 84 mSoundEffectListeners = new ArrayMap<>(); 85 private final IVirtualDeviceActivityListener mActivityListenerBinder = 86 new IVirtualDeviceActivityListener.Stub() { 87 88 @Override 89 public void onTopActivityChanged(int displayId, ComponentName topActivity, 90 @UserIdInt int userId) { 91 final long token = Binder.clearCallingIdentity(); 92 try { 93 synchronized (mActivityListenersLock) { 94 for (int i = 0; i < mActivityListeners.size(); i++) { 95 mActivityListeners.valueAt(i) 96 .onTopActivityChanged(displayId, topActivity); 97 mActivityListeners.valueAt(i) 98 .onTopActivityChanged(displayId, topActivity, userId); 99 } 100 } 101 } finally { 102 Binder.restoreCallingIdentity(token); 103 } 104 } 105 106 @Override 107 public void onDisplayEmpty(int displayId) { 108 final long token = Binder.clearCallingIdentity(); 109 try { 110 synchronized (mActivityListenersLock) { 111 for (int i = 0; i < mActivityListeners.size(); i++) { 112 mActivityListeners.valueAt(i).onDisplayEmpty(displayId); 113 } 114 } 115 } finally { 116 Binder.restoreCallingIdentity(token); 117 } 118 } 119 }; 120 private final IVirtualDeviceSoundEffectListener mSoundEffectListener = 121 new IVirtualDeviceSoundEffectListener.Stub() { 122 @Override 123 public void onPlaySoundEffect(int soundEffect) { 124 final long token = Binder.clearCallingIdentity(); 125 try { 126 synchronized (mSoundEffectListenersLock) { 127 for (int i = 0; i < mSoundEffectListeners.size(); i++) { 128 mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect); 129 } 130 } 131 } finally { 132 Binder.restoreCallingIdentity(token); 133 } 134 } 135 }; 136 @Nullable 137 private VirtualAudioDevice mVirtualAudioDevice; 138 VirtualDeviceInternal( IVirtualDeviceManager service, Context context, int associationId, VirtualDeviceParams params)139 VirtualDeviceInternal( 140 IVirtualDeviceManager service, 141 Context context, 142 int associationId, 143 VirtualDeviceParams params) throws RemoteException { 144 mService = service; 145 mContext = context.getApplicationContext(); 146 mVirtualDevice = service.createVirtualDevice( 147 new Binder(), 148 mContext.getPackageName(), 149 associationId, 150 params, 151 mActivityListenerBinder, 152 mSoundEffectListener); 153 } 154 getDeviceId()155 int getDeviceId() { 156 try { 157 return mVirtualDevice.getDeviceId(); 158 } catch (RemoteException e) { 159 throw e.rethrowFromSystemServer(); 160 } 161 } 162 createContext()163 @NonNull Context createContext() { 164 try { 165 return mContext.createDeviceContext(mVirtualDevice.getDeviceId()); 166 } catch (RemoteException e) { 167 throw e.rethrowFromSystemServer(); 168 } 169 } 170 171 @NonNull getVirtualSensorList()172 List<VirtualSensor> getVirtualSensorList() { 173 try { 174 return mVirtualDevice.getVirtualSensorList(); 175 } catch (RemoteException e) { 176 throw e.rethrowFromSystemServer(); 177 } 178 } 179 launchPendingIntent( int displayId, @NonNull PendingIntent pendingIntent, @NonNull Executor executor, @NonNull IntConsumer listener)180 void launchPendingIntent( 181 int displayId, 182 @NonNull PendingIntent pendingIntent, 183 @NonNull Executor executor, 184 @NonNull IntConsumer listener) { 185 try { 186 mVirtualDevice.launchPendingIntent( 187 displayId, 188 pendingIntent, 189 new ResultReceiver(new Handler(Looper.getMainLooper())) { 190 @Override 191 protected void onReceiveResult(int resultCode, Bundle resultData) { 192 super.onReceiveResult(resultCode, resultData); 193 executor.execute(() -> listener.accept(resultCode)); 194 } 195 }); 196 } catch (RemoteException e) { 197 e.rethrowFromSystemServer(); 198 } 199 } 200 201 @Nullable createVirtualDisplay( @onNull VirtualDisplayConfig config, @Nullable @CallbackExecutor Executor executor, @Nullable VirtualDisplay.Callback callback)202 VirtualDisplay createVirtualDisplay( 203 @NonNull VirtualDisplayConfig config, 204 @Nullable @CallbackExecutor Executor executor, 205 @Nullable VirtualDisplay.Callback callback) { 206 IVirtualDisplayCallback callbackWrapper = 207 new DisplayManagerGlobal.VirtualDisplayCallback(callback, executor); 208 final int displayId; 209 try { 210 displayId = mService.createVirtualDisplay(config, callbackWrapper, mVirtualDevice, 211 mContext.getPackageName()); 212 } catch (RemoteException ex) { 213 throw ex.rethrowFromSystemServer(); 214 } 215 DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance(); 216 return displayManager.createVirtualDisplayWrapper(config, callbackWrapper, 217 displayId); 218 } 219 close()220 void close() { 221 try { 222 // This also takes care of unregistering all virtual sensors. 223 mVirtualDevice.close(); 224 } catch (RemoteException e) { 225 throw e.rethrowFromSystemServer(); 226 } 227 if (mVirtualAudioDevice != null) { 228 mVirtualAudioDevice.close(); 229 mVirtualAudioDevice = null; 230 } 231 } 232 233 @NonNull createVirtualDpad(@onNull VirtualDpadConfig config)234 VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) { 235 try { 236 final IBinder token = new Binder( 237 "android.hardware.input.VirtualDpad:" + config.getInputDeviceName()); 238 mVirtualDevice.createVirtualDpad(config, token); 239 return new VirtualDpad(mVirtualDevice, token); 240 } catch (RemoteException e) { 241 throw e.rethrowFromSystemServer(); 242 } 243 } 244 245 @NonNull createVirtualKeyboard(@onNull VirtualKeyboardConfig config)246 VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) { 247 try { 248 final IBinder token = new Binder( 249 "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName()); 250 mVirtualDevice.createVirtualKeyboard(config, token); 251 return new VirtualKeyboard(mVirtualDevice, token); 252 } catch (RemoteException e) { 253 throw e.rethrowFromSystemServer(); 254 } 255 } 256 257 @NonNull createVirtualMouse(@onNull VirtualMouseConfig config)258 VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) { 259 try { 260 final IBinder token = new Binder( 261 "android.hardware.input.VirtualMouse:" + config.getInputDeviceName()); 262 mVirtualDevice.createVirtualMouse(config, token); 263 return new VirtualMouse(mVirtualDevice, token); 264 } catch (RemoteException e) { 265 throw e.rethrowFromSystemServer(); 266 } 267 } 268 269 @NonNull createVirtualTouchscreen( @onNull VirtualTouchscreenConfig config)270 VirtualTouchscreen createVirtualTouchscreen( 271 @NonNull VirtualTouchscreenConfig config) { 272 try { 273 final IBinder token = new Binder( 274 "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName()); 275 mVirtualDevice.createVirtualTouchscreen(config, token); 276 return new VirtualTouchscreen(mVirtualDevice, token); 277 } catch (RemoteException e) { 278 throw e.rethrowFromSystemServer(); 279 } 280 } 281 282 @NonNull createVirtualNavigationTouchpad( @onNull VirtualNavigationTouchpadConfig config)283 VirtualNavigationTouchpad createVirtualNavigationTouchpad( 284 @NonNull VirtualNavigationTouchpadConfig config) { 285 try { 286 final IBinder token = new Binder( 287 "android.hardware.input.VirtualNavigationTouchpad:" 288 + config.getInputDeviceName()); 289 mVirtualDevice.createVirtualNavigationTouchpad(config, token); 290 return new VirtualNavigationTouchpad(mVirtualDevice, token); 291 } catch (RemoteException e) { 292 throw e.rethrowFromSystemServer(); 293 } 294 } 295 296 @NonNull createVirtualAudioDevice( @onNull VirtualDisplay display, @Nullable Executor executor, @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback)297 VirtualAudioDevice createVirtualAudioDevice( 298 @NonNull VirtualDisplay display, 299 @Nullable Executor executor, 300 @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback) { 301 if (mVirtualAudioDevice == null) { 302 mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display, 303 executor, callback, () -> mVirtualAudioDevice = null); 304 } 305 return mVirtualAudioDevice; 306 } 307 308 @NonNull setShowPointerIcon(boolean showPointerIcon)309 void setShowPointerIcon(boolean showPointerIcon) { 310 try { 311 mVirtualDevice.setShowPointerIcon(showPointerIcon); 312 } catch (RemoteException e) { 313 throw e.rethrowFromSystemServer(); 314 } 315 } 316 addActivityListener( @allbackExecutor @onNull Executor executor, @NonNull VirtualDeviceManager.ActivityListener listener)317 void addActivityListener( 318 @CallbackExecutor @NonNull Executor executor, 319 @NonNull VirtualDeviceManager.ActivityListener listener) { 320 final ActivityListenerDelegate delegate = new ActivityListenerDelegate( 321 Objects.requireNonNull(listener), Objects.requireNonNull(executor)); 322 synchronized (mActivityListenersLock) { 323 mActivityListeners.put(listener, delegate); 324 } 325 } 326 removeActivityListener(@onNull VirtualDeviceManager.ActivityListener listener)327 void removeActivityListener(@NonNull VirtualDeviceManager.ActivityListener listener) { 328 synchronized (mActivityListenersLock) { 329 mActivityListeners.remove(Objects.requireNonNull(listener)); 330 } 331 } 332 addSoundEffectListener(@allbackExecutor @onNull Executor executor, @NonNull VirtualDeviceManager.SoundEffectListener soundEffectListener)333 void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor, 334 @NonNull VirtualDeviceManager.SoundEffectListener soundEffectListener) { 335 final SoundEffectListenerDelegate delegate = 336 new SoundEffectListenerDelegate(Objects.requireNonNull(executor), 337 Objects.requireNonNull(soundEffectListener)); 338 synchronized (mSoundEffectListenersLock) { 339 mSoundEffectListeners.put(soundEffectListener, delegate); 340 } 341 } 342 removeSoundEffectListener( @onNull VirtualDeviceManager.SoundEffectListener soundEffectListener)343 void removeSoundEffectListener( 344 @NonNull VirtualDeviceManager.SoundEffectListener soundEffectListener) { 345 synchronized (mSoundEffectListenersLock) { 346 mSoundEffectListeners.remove(Objects.requireNonNull(soundEffectListener)); 347 } 348 } 349 registerIntentInterceptor( @onNull IntentFilter interceptorFilter, @CallbackExecutor @NonNull Executor executor, @NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback)350 void registerIntentInterceptor( 351 @NonNull IntentFilter interceptorFilter, 352 @CallbackExecutor @NonNull Executor executor, 353 @NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) { 354 Objects.requireNonNull(executor); 355 Objects.requireNonNull(interceptorFilter); 356 Objects.requireNonNull(interceptorCallback); 357 final IntentInterceptorDelegate delegate = 358 new IntentInterceptorDelegate(executor, interceptorCallback); 359 try { 360 mVirtualDevice.registerIntentInterceptor(delegate, interceptorFilter); 361 } catch (RemoteException e) { 362 throw e.rethrowFromSystemServer(); 363 } 364 synchronized (mIntentInterceptorListenersLock) { 365 mIntentInterceptorListeners.put(interceptorCallback, delegate); 366 } 367 } 368 unregisterIntentInterceptor( @onNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback)369 void unregisterIntentInterceptor( 370 @NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) { 371 Objects.requireNonNull(interceptorCallback); 372 final IntentInterceptorDelegate delegate; 373 synchronized (mIntentInterceptorListenersLock) { 374 delegate = mIntentInterceptorListeners.remove(interceptorCallback); 375 } 376 if (delegate != null) { 377 try { 378 mVirtualDevice.unregisterIntentInterceptor(delegate); 379 } catch (RemoteException e) { 380 throw e.rethrowFromSystemServer(); 381 } 382 } 383 } 384 385 /** 386 * A wrapper for {@link VirtualDeviceManager.ActivityListener} that executes callbacks on the 387 * given executor. 388 */ 389 private static class ActivityListenerDelegate { 390 @NonNull private final VirtualDeviceManager.ActivityListener mActivityListener; 391 @NonNull private final Executor mExecutor; 392 ActivityListenerDelegate(@onNull VirtualDeviceManager.ActivityListener listener, @NonNull Executor executor)393 ActivityListenerDelegate(@NonNull VirtualDeviceManager.ActivityListener listener, 394 @NonNull Executor executor) { 395 mActivityListener = listener; 396 mExecutor = executor; 397 } 398 onTopActivityChanged(int displayId, ComponentName topActivity)399 public void onTopActivityChanged(int displayId, ComponentName topActivity) { 400 mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity)); 401 } 402 onTopActivityChanged(int displayId, ComponentName topActivity, @UserIdInt int userId)403 public void onTopActivityChanged(int displayId, ComponentName topActivity, 404 @UserIdInt int userId) { 405 mExecutor.execute(() -> 406 mActivityListener.onTopActivityChanged(displayId, topActivity, userId)); 407 } 408 onDisplayEmpty(int displayId)409 public void onDisplayEmpty(int displayId) { 410 mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId)); 411 } 412 } 413 414 /** 415 * A wrapper for {@link VirtualDeviceManager.IntentInterceptorCallback} that executes callbacks 416 * on the given executor. 417 */ 418 private static class IntentInterceptorDelegate extends IVirtualDeviceIntentInterceptor.Stub { 419 @NonNull private final VirtualDeviceManager.IntentInterceptorCallback 420 mIntentInterceptorCallback; 421 @NonNull private final Executor mExecutor; 422 IntentInterceptorDelegate(Executor executor, VirtualDeviceManager.IntentInterceptorCallback interceptorCallback)423 private IntentInterceptorDelegate(Executor executor, 424 VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) { 425 mExecutor = executor; 426 mIntentInterceptorCallback = interceptorCallback; 427 } 428 429 @Override onIntentIntercepted(Intent intent)430 public void onIntentIntercepted(Intent intent) { 431 final long token = Binder.clearCallingIdentity(); 432 try { 433 mExecutor.execute(() -> mIntentInterceptorCallback.onIntentIntercepted(intent)); 434 } finally { 435 Binder.restoreCallingIdentity(token); 436 } 437 } 438 } 439 440 /** 441 * A wrapper for {@link VirtualDeviceManager.SoundEffectListener} that executes callbacks on the 442 * given executor. 443 */ 444 private static class SoundEffectListenerDelegate { 445 @NonNull private final VirtualDeviceManager.SoundEffectListener mSoundEffectListener; 446 @NonNull private final Executor mExecutor; 447 SoundEffectListenerDelegate(Executor executor, VirtualDeviceManager.SoundEffectListener soundEffectCallback)448 private SoundEffectListenerDelegate(Executor executor, 449 VirtualDeviceManager.SoundEffectListener soundEffectCallback) { 450 mSoundEffectListener = soundEffectCallback; 451 mExecutor = executor; 452 } 453 onPlaySoundEffect(@udioManager.SystemSoundEffect int effectType)454 public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) { 455 mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType)); 456 } 457 } 458 } 459