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