• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.example.android.vdmdemo.host;
18 
19 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
20 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
21 import static android.companion.virtual.VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED;
22 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
23 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
24 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
25 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
26 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
27 
28 import android.annotation.SuppressLint;
29 import android.app.Notification;
30 import android.app.NotificationChannel;
31 import android.app.NotificationManager;
32 import android.app.PendingIntent;
33 import android.app.Service;
34 import android.app.role.RoleManager;
35 import android.companion.AssociationInfo;
36 import android.companion.AssociationRequest;
37 import android.companion.CompanionDeviceManager;
38 import android.companion.virtual.VirtualDeviceManager;
39 import android.companion.virtual.VirtualDeviceManager.ActivityListener;
40 import android.companion.virtual.VirtualDeviceParams;
41 import android.companion.virtual.sensor.VirtualSensorConfig;
42 import android.content.ComponentName;
43 import android.content.Intent;
44 import android.content.IntentSender;
45 import android.content.IntentSender.SendIntentException;
46 import android.content.pm.ActivityInfo;
47 import android.content.pm.PackageManager.NameNotFoundException;
48 import android.graphics.drawable.Icon;
49 import android.hardware.display.DisplayManager;
50 import android.media.AudioManager;
51 import android.os.Binder;
52 import android.os.IBinder;
53 import android.util.Log;
54 import android.view.Display;
55 
56 import androidx.annotation.NonNull;
57 import androidx.core.os.BuildCompat;
58 
59 import com.example.android.vdmdemo.common.ConnectionManager;
60 import com.example.android.vdmdemo.common.RemoteEventProto.DeviceCapabilities;
61 import com.example.android.vdmdemo.common.RemoteEventProto.DisplayChangeEvent;
62 import com.example.android.vdmdemo.common.RemoteEventProto.RemoteEvent;
63 import com.example.android.vdmdemo.common.RemoteEventProto.SensorCapabilities;
64 import com.example.android.vdmdemo.common.RemoteEventProto.StartStreaming;
65 import com.example.android.vdmdemo.common.RemoteIo;
66 import com.google.common.util.concurrent.MoreExecutors;
67 
68 import dagger.hilt.android.AndroidEntryPoint;
69 
70 import java.util.Arrays;
71 import java.util.HashMap;
72 import java.util.Map;
73 import java.util.Objects;
74 import java.util.concurrent.Executors;
75 import java.util.function.Consumer;
76 
77 import javax.inject.Inject;
78 
79 /**
80  * VDM Host service, streaming apps to a remote device and processing the input coming from there.
81  */
82 @AndroidEntryPoint(Service.class)
83 @SuppressLint("NewApi")
84 public final class VdmService extends Hilt_VdmService {
85 
86     public static final String TAG = "VdmHost";
87 
88     private static final String CHANNEL_ID = "com.example.android.vdmdemo.host.VdmService";
89     private static final int NOTIFICATION_ID = 1;
90 
91     private static final String ACTION_STOP = "com.example.android.vdmdemo.host.VdmService.STOP";
92 
93     public static final String ACTION_LOCKDOWN =
94             "com.example.android.vdmdemo.host.VdmService.LOCKDOWN";
95     private int mRecordingAudioSessionId;
96     private int mPlaybackAudioSessionId;
97 
98     /** Provides an instance of this service to bound clients. */
99     public class LocalBinder extends Binder {
getService()100         VdmService getService() {
101             return VdmService.this;
102         }
103     }
104 
105     private final IBinder mBinder = new LocalBinder();
106 
107     private final Map<Integer, Consumer<Object>> mPreferenceObservers = createPreferenceObservers();
108 
109     @Inject
110     ConnectionManager mConnectionManager;
111     @Inject
112     RemoteIo mRemoteIo;
113     @Inject
114     AudioStreamer mAudioStreamer;
115     @Inject
116     AudioInjector mAudioInjector;
117     @Inject
118     PreferenceController mPreferenceController;
119     @Inject
120     DisplayRepository mDisplayRepository;
121     @Inject
122     InputController mInputController;
123 
124     private RemoteSensorManager mRemoteSensorManager = null;
125 
126     private RemoteCameraManager mRemoteCameraManager;
127     private final Consumer<RemoteEvent> mRemoteEventConsumer = this::processRemoteEvent;
128     private VirtualDeviceManager.VirtualDevice mVirtualDevice;
129     private DeviceCapabilities mDeviceCapabilities;
130     private Intent mPendingRemoteIntent = null;
131     private @RemoteDisplay.DisplayType int mPendingDisplayType = RemoteDisplay.DISPLAY_TYPE_APP;
132     private DisplayManager mDisplayManager;
133     private VirtualDeviceManager mVirtualDeviceManager;
134     private Consumer<Boolean> mLocalVirtualDeviceLifecycleListener;
135 
136     private VirtualDeviceManager.VirtualDeviceListener mVirtualDeviceListener;
137 
138     private final DisplayManager.DisplayListener mDisplayListener =
139             new DisplayManager.DisplayListener() {
140                 @Override
141                 public void onDisplayAdded(int displayId) {
142                 }
143 
144                 @Override
145                 public void onDisplayRemoved(int displayId) {
146                 }
147 
148                 @Override
149                 public void onDisplayChanged(int displayId) {
150                     mDisplayRepository.onDisplayChanged(displayId);
151                 }
152             };
153 
154     private final Consumer<ConnectionManager.ConnectionStatus> mConnectionCallback =
155             (status) -> {
156                 if (status.state != ConnectionManager.ConnectionStatus.State.CONNECTED) {
157                     mDeviceCapabilities = null;
158                     closeVirtualDevice();
159                 }
160             };
161 
VdmService()162     public VdmService() {
163     }
164 
165     @Override
onBind(Intent intent)166     public IBinder onBind(Intent intent) {
167         return mBinder;
168     }
169 
170     @Override
onStartCommand(Intent intent, int flags, int startId)171     public int onStartCommand(Intent intent, int flags, int startId) {
172         if (intent != null && ACTION_STOP.equals(intent.getAction())) {
173             Log.i(TAG, "Stopping VDM Service.");
174             mConnectionManager.disconnect();
175             stopForeground(STOP_FOREGROUND_REMOVE);
176             stopSelf();
177             return START_NOT_STICKY;
178         }
179 
180         if (intent != null && ACTION_LOCKDOWN.equals(intent.getAction())) {
181             lockdown();
182             return START_STICKY;
183         }
184 
185         NotificationChannel notificationChannel =
186                 new NotificationChannel(
187                         CHANNEL_ID, "VDM Service Channel", NotificationManager.IMPORTANCE_LOW);
188         notificationChannel.enableVibration(false);
189         NotificationManager notificationManager = getSystemService(NotificationManager.class);
190         Objects.requireNonNull(notificationManager).createNotificationChannel(notificationChannel);
191 
192         Intent openIntent = new Intent(this, MainActivity.class);
193         PendingIntent pendingIntentOpen =
194                 PendingIntent.getActivity(this, 0, openIntent, PendingIntent.FLAG_IMMUTABLE);
195         Intent stopIntent = new Intent(this, VdmService.class);
196         stopIntent.setAction(ACTION_STOP);
197         PendingIntent pendingIntentStop =
198                 PendingIntent.getService(this, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE);
199 
200         Notification notification =
201                 new Notification.Builder(this, CHANNEL_ID)
202                         .setSmallIcon(R.drawable.connected)
203                         .setContentTitle("VDM Demo running")
204                         .setContentText("Click to open")
205                         .setContentIntent(pendingIntentOpen)
206                         .addAction(
207                                 new Notification.Action.Builder(
208                                         Icon.createWithResource("", R.drawable.close), "Stop",
209                                         pendingIntentStop)
210                                         .build())
211                         .setOngoing(true)
212                         .build();
213         startForeground(NOTIFICATION_ID, notification);
214 
215         return START_STICKY;
216     }
217 
218     @Override
onCreate()219     public void onCreate() {
220         super.onCreate();
221 
222         mConnectionManager.addConnectionCallback(mConnectionCallback);
223         mConnectionManager.startHostSession();
224 
225         mDisplayManager = getSystemService(DisplayManager.class);
226         Objects.requireNonNull(mDisplayManager).registerDisplayListener(mDisplayListener, null);
227 
228         mRemoteIo.addMessageConsumer(mRemoteEventConsumer);
229 
230         mPreferenceController.addPreferenceObserver(this, mPreferenceObservers);
231 
232         mVirtualDeviceManager =
233                 Objects.requireNonNull(getSystemService(VirtualDeviceManager.class));
234 
235         if (BuildCompat.isAtLeastV()) {
236             mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() {
237                 @Override
238                 public void onVirtualDeviceClosed(int deviceId) {
239                     if (mVirtualDevice != null && mVirtualDevice.getDeviceId() == deviceId) {
240                         closeVirtualDevice();
241                     }
242                 }
243             };
244             mVirtualDeviceManager.registerVirtualDeviceListener(
245                     Executors.newSingleThreadExecutor(), mVirtualDeviceListener);
246         }
247     }
248 
249     @Override
onDestroy()250     public void onDestroy() {
251         super.onDestroy();
252         if (BuildCompat.isAtLeastV()) {
253             mVirtualDeviceManager.unregisterVirtualDeviceListener(mVirtualDeviceListener);
254         }
255         mPreferenceController.removePreferenceObserver(this);
256         mConnectionManager.removeConnectionCallback(mConnectionCallback);
257         closeVirtualDevice();
258         mRemoteIo.removeMessageConsumer(mRemoteEventConsumer);
259         mDisplayManager.unregisterDisplayListener(mDisplayListener);
260         mAudioStreamer.stop();
261         mAudioInjector.stop();
262     }
263 
setVirtualDeviceListener(Consumer<Boolean> listener)264     void setVirtualDeviceListener(Consumer<Boolean> listener) {
265         mLocalVirtualDeviceLifecycleListener = listener;
266     }
267 
processRemoteEvent(RemoteEvent event)268     private void processRemoteEvent(RemoteEvent event) {
269         if (event.hasDeviceCapabilities()) {
270             mDeviceCapabilities = event.getDeviceCapabilities();
271             associateAndCreateVirtualDevice();
272             handleAudioCapabilities();
273         } else if (event.hasDisplayCapabilities() && !mDisplayRepository.resetDisplay(event)) {
274             RemoteDisplay remoteDisplay =
275                     new RemoteDisplay(
276                             this,
277                             event,
278                             mVirtualDevice,
279                             mRemoteIo,
280                             mPendingDisplayType,
281                             mPreferenceController);
282             mDisplayRepository.addDisplay(remoteDisplay);
283             mPendingDisplayType = RemoteDisplay.DISPLAY_TYPE_APP;
284             if (mPendingRemoteIntent != null) {
285                 remoteDisplay.launchIntent(mPendingRemoteIntent);
286                 mPendingRemoteIntent = null;
287             }
288         } else if (event.hasStopStreaming() && !event.getStopStreaming().getPause()) {
289             mDisplayRepository.removeDisplayByRemoteId(event.getDisplayId());
290         } else if (event.hasDisplayChangeEvent() && event.getDisplayChangeEvent().getFocused()) {
291             mInputController.setFocusedRemoteDisplayId(event.getDisplayId());
292         }
293     }
294 
handleAudioCapabilities()295     private void handleAudioCapabilities() {
296         if (mPreferenceController.getBoolean(R.string.pref_enable_client_audio)) {
297             if (mVirtualDevice != null) {
298                 if (mDeviceCapabilities.getSupportsAudioOutput()) {
299                     mAudioStreamer.start(mVirtualDevice.getDeviceId(), mPlaybackAudioSessionId);
300                 }
301                 if (mDeviceCapabilities.getSupportsAudioInput()) {
302                     mAudioInjector.start(mVirtualDevice.getDeviceId(), mRecordingAudioSessionId);
303                 }
304             }
305         } else {
306             mAudioStreamer.stop();
307             mAudioInjector.stop();
308         }
309     }
310 
associateAndCreateVirtualDevice()311     private void associateAndCreateVirtualDevice() {
312         CompanionDeviceManager cdm =
313                 Objects.requireNonNull(getSystemService(CompanionDeviceManager.class));
314         RoleManager rm = Objects.requireNonNull(getSystemService(RoleManager.class));
315         final String deviceProfile = mPreferenceController.getString(R.string.pref_device_profile);
316         for (AssociationInfo associationInfo : cdm.getMyAssociations()) {
317             if (!Objects.equals(associationInfo.getDeviceProfile(), deviceProfile)
318                     || !Objects.equals(associationInfo.getPackageName(), getPackageName())
319                     || associationInfo.getDisplayName() == null
320                     || !Objects.equals(
321                     associationInfo.getDisplayName().toString(),
322                     mDeviceCapabilities.getDeviceName())) {
323                 continue;
324             }
325             // It is possible that the role was revoked but the CDM association remained.
326             if (!rm.isRoleHeld(deviceProfile)) {
327                 cdm.disassociate(associationInfo.getId());
328                 break;
329             } else {
330                 createVirtualDevice(associationInfo);
331                 return;
332             }
333         }
334 
335         @SuppressLint("MissingPermission")
336         AssociationRequest.Builder associationRequest =
337                 new AssociationRequest.Builder()
338                         .setDeviceProfile(deviceProfile)
339                         .setDisplayName(mDeviceCapabilities.getDeviceName())
340                         .setSelfManaged(true);
341         cdm.associate(
342                 associationRequest.build(),
343                 new CompanionDeviceManager.Callback() {
344                     @Override
345                     public void onAssociationPending(@NonNull IntentSender intentSender) {
346                         try {
347                             startIntentSender(intentSender, null, 0, 0, 0);
348                         } catch (SendIntentException e) {
349                             Log.e(
350                                     TAG,
351                                     "onAssociationPending: Failed to send device selection intent",
352                                     e);
353                         }
354                     }
355 
356                     @Override
357                     public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {
358                         Log.i(TAG, "onAssociationCreated: ID " + associationInfo.getId());
359                         createVirtualDevice(associationInfo);
360                     }
361 
362                     @Override
363                     public void onFailure(CharSequence error) {
364                         Log.e(TAG, "onFailure: RemoteDevice Association failed " + error);
365                     }
366                 },
367                 null);
368         Log.i(TAG, "createCdmAssociation: Waiting for association to happen");
369     }
370 
createVirtualDevice(AssociationInfo associationInfo)371     private void createVirtualDevice(AssociationInfo associationInfo) {
372         VirtualDeviceParams.Builder virtualDeviceBuilder =
373                 new VirtualDeviceParams.Builder()
374                         .setName("VirtualDevice - " + mDeviceCapabilities.getDeviceName());
375 
376         AudioManager audioManager = getSystemService(AudioManager.class);
377         mPlaybackAudioSessionId = audioManager.generateAudioSessionId();
378         mRecordingAudioSessionId = audioManager.generateAudioSessionId();
379 
380         if (mPreferenceController.getBoolean(R.string.pref_enable_client_audio)) {
381             virtualDeviceBuilder.setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
382                     .setAudioPlaybackSessionId(mPlaybackAudioSessionId)
383                     .setAudioRecordingSessionId(mRecordingAudioSessionId);
384         }
385 
386         if (mPreferenceController.getBoolean(R.string.pref_always_unlocked_device)) {
387             virtualDeviceBuilder.setLockState(LOCK_STATE_ALWAYS_UNLOCKED);
388         }
389 
390         if (mPreferenceController.getBoolean(R.string.pref_enable_custom_home)) {
391             virtualDeviceBuilder.setHomeComponent(
392                     new ComponentName(this, CustomLauncherActivity.class));
393         }
394 
395         if (mPreferenceController.getBoolean(R.string.pref_hide_from_recents)) {
396             virtualDeviceBuilder.setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_CUSTOM);
397         }
398 
399         if (mPreferenceController.getBoolean(R.string.pref_enable_cross_device_clipboard)) {
400             virtualDeviceBuilder.setDevicePolicy(POLICY_TYPE_CLIPBOARD, DEVICE_POLICY_CUSTOM);
401         }
402 
403         if (mPreferenceController.getBoolean(R.string.pref_enable_client_native_ime)) {
404             virtualDeviceBuilder.setInputMethodComponent(
405                     new ComponentName(this, VdmProxyIme.class));
406         }
407 
408         if (mPreferenceController.getBoolean(R.string.pref_enable_client_sensors)) {
409             for (SensorCapabilities sensor : mDeviceCapabilities.getSensorCapabilitiesList()) {
410                 virtualDeviceBuilder.addVirtualSensorConfig(
411                         new VirtualSensorConfig.Builder(
412                                 sensor.getType(), "Remote-" + sensor.getName())
413                                 .setMinDelay(sensor.getMinDelayUs())
414                                 .setMaxDelay(sensor.getMaxDelayUs())
415                                 .setPower(sensor.getPower())
416                                 .setResolution(sensor.getResolution())
417                                 .setMaximumRange(sensor.getMaxRange())
418                                 .build());
419             }
420 
421             if (mDeviceCapabilities.getSensorCapabilitiesCount() > 0) {
422                 mRemoteSensorManager = new RemoteSensorManager(mRemoteIo);
423                 virtualDeviceBuilder
424                         .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
425                         .setVirtualSensorCallback(
426                                 MoreExecutors.directExecutor(),
427                                 mRemoteSensorManager.getVirtualSensorCallback());
428             }
429         }
430 
431         if (mPreferenceController.getBoolean(R.string.pref_enable_client_camera)) {
432             virtualDeviceBuilder.setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM);
433         }
434 
435         mVirtualDevice = mVirtualDeviceManager
436                 .createVirtualDevice(associationInfo.getId(), virtualDeviceBuilder.build());
437         if (mRemoteSensorManager != null) {
438             mRemoteSensorManager.setVirtualSensors(mVirtualDevice.getVirtualSensorList());
439         }
440 
441         mVirtualDevice.setShowPointerIcon(
442                 mPreferenceController.getBoolean(R.string.pref_show_pointer_icon));
443 
444         mVirtualDevice.addActivityListener(
445                 MoreExecutors.directExecutor(),
446                 new ActivityListener() {
447 
448                     @Override
449                     public void onTopActivityChanged(
450                             int displayId, @NonNull ComponentName componentName) {
451                         Log.w(TAG, "onTopActivityChanged " + displayId + ": " + componentName);
452                         int remoteDisplayId = mDisplayRepository.getRemoteDisplayId(displayId);
453                         if (remoteDisplayId == Display.INVALID_DISPLAY) {
454                             return;
455                         }
456 
457                         String title = "";
458                         try {
459                             ActivityInfo activityInfo =
460                                     getPackageManager().getActivityInfo(componentName, 0);
461                             title = activityInfo.loadLabel(getPackageManager()).toString();
462                         } catch (NameNotFoundException e) {
463                             Log.w(TAG, "Failed to get activity label for " + componentName);
464                         }
465                         mRemoteIo.sendMessage(
466                                 RemoteEvent.newBuilder()
467                                         .setDisplayId(remoteDisplayId)
468                                         .setDisplayChangeEvent(
469                                                 DisplayChangeEvent.newBuilder().setTitle(title))
470                                         .build());
471                     }
472 
473                     @Override
474                     public void onDisplayEmpty(int displayId) {
475                         Log.i(TAG, "Display " + displayId + " is empty, removing");
476                         mDisplayRepository.removeDisplay(displayId);
477                     }
478                 });
479         mVirtualDevice.addActivityListener(
480                 MoreExecutors.directExecutor(),
481                 new RunningVdmUidsTracker(getApplicationContext(), mPreferenceController,
482                         mAudioStreamer, mAudioInjector));
483 
484         if (mPreferenceController.getBoolean(R.string.pref_enable_client_camera)) {
485             if (mRemoteCameraManager != null) {
486                 mRemoteCameraManager.close();
487             }
488             mRemoteCameraManager = new RemoteCameraManager(mVirtualDevice, mRemoteIo);
489             mRemoteCameraManager.createCameras(mDeviceCapabilities.getCameraCapabilitiesList());
490         }
491 
492         Log.i(TAG, "Created virtual device");
493         if (mLocalVirtualDeviceLifecycleListener != null) {
494             mLocalVirtualDeviceLifecycleListener.accept(true);
495         }
496     }
497 
lockdown()498     private void lockdown() {
499         Log.i(TAG, "Initiating Lockdown.");
500         mDisplayRepository.clear();
501     }
502 
closeVirtualDevice()503     private synchronized void closeVirtualDevice() {
504         if (mLocalVirtualDeviceLifecycleListener != null) {
505             mLocalVirtualDeviceLifecycleListener.accept(false);
506         }
507         if (mRemoteSensorManager != null) {
508             mRemoteSensorManager.close();
509             mRemoteSensorManager = null;
510         }
511         if (mVirtualDevice != null) {
512             Log.i(TAG, "Closing virtual device");
513             mDisplayRepository.clear();
514             mVirtualDevice.close();
515             mVirtualDevice = null;
516         }
517     }
518 
isVirtualDeviceActive()519     boolean isVirtualDeviceActive() {
520         return mVirtualDevice != null;
521     }
522 
getRemoteDisplayIds()523     int[] getRemoteDisplayIds() {
524         return mDisplayRepository.getRemoteDisplayIds();
525     }
526 
startStreamingHome()527     void startStreamingHome() {
528         mPendingRemoteIntent = null;
529         mPendingDisplayType = RemoteDisplay.DISPLAY_TYPE_HOME;
530         mRemoteIo.sendMessage(RemoteEvent.newBuilder()
531                 .setStartStreaming(StartStreaming.newBuilder().setHomeEnabled(true)).build());
532     }
533 
startMirroring()534     void startMirroring() {
535         mPendingRemoteIntent = null;
536         mPendingDisplayType = RemoteDisplay.DISPLAY_TYPE_MIRROR;
537         mRemoteIo.sendMessage(
538                 RemoteEvent.newBuilder()
539                         .setStartStreaming(StartStreaming.newBuilder().setHomeEnabled(true))
540                         .build());
541     }
542 
startStreaming(Intent intent)543     void startStreaming(Intent intent) {
544         mPendingRemoteIntent = intent;
545         mPendingRemoteIntent.addFlags(
546                 Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
547         mRemoteIo.sendMessage(
548                 RemoteEvent.newBuilder().setStartStreaming(StartStreaming.newBuilder()).build());
549     }
550 
startIntentOnDisplayIndex(Intent intent, int displayIndex)551     void startIntentOnDisplayIndex(Intent intent, int displayIndex) {
552         mDisplayRepository
553                 .getDisplayByIndex(displayIndex)
554                 .ifPresent(d -> d.launchIntent(intent));
555     }
556 
recreateVirtualDevice()557     private void recreateVirtualDevice() {
558         if (mVirtualDevice != null) {
559             closeVirtualDevice();
560             if (mDeviceCapabilities != null) {
561                 associateAndCreateVirtualDevice();
562             }
563         }
564     }
565 
updateDevicePolicy(int policyType, boolean custom)566     private void updateDevicePolicy(int policyType, boolean custom) {
567         if (!BuildCompat.isAtLeastV()) {
568             recreateVirtualDevice();
569         } else if (mVirtualDevice != null) {
570             mVirtualDevice.setDevicePolicy(
571                     policyType, custom ? DEVICE_POLICY_CUSTOM : DEVICE_POLICY_DEFAULT);
572         }
573     }
574 
createPreferenceObservers()575     private Map<Integer, Consumer<Object>> createPreferenceObservers() {
576         HashMap<Integer, Consumer<Object>> observers = new HashMap<>();
577 
578         observers.put(R.string.pref_hide_from_recents,
579                 b -> updateDevicePolicy(POLICY_TYPE_RECENTS, (Boolean) b));
580         observers.put(R.string.pref_enable_cross_device_clipboard,
581                 b -> updateDevicePolicy(POLICY_TYPE_CLIPBOARD, (Boolean) b));
582         observers.put(R.string.pref_show_pointer_icon,
583                 b -> {
584                     if (mVirtualDevice != null) mVirtualDevice.setShowPointerIcon((Boolean) b);
585                 });
586         observers.put(R.string.pref_enable_client_audio, b -> handleAudioCapabilities());
587         observers.put(R.string.pref_display_ime_policy,
588                 s -> {
589                     if (mVirtualDevice != null) {
590                         int policy = Integer.valueOf((String) s);
591                         Arrays.stream(mDisplayRepository.getDisplayIds()).forEach(
592                                 displayId -> mVirtualDevice.setDisplayImePolicy(displayId, policy));
593                     }
594                 });
595         observers.put(R.string.pref_enable_client_camera, v -> recreateVirtualDevice());
596         observers.put(R.string.pref_enable_client_sensors, v -> recreateVirtualDevice());
597         observers.put(R.string.pref_device_profile, v -> recreateVirtualDevice());
598         observers.put(R.string.pref_always_unlocked_device, v -> recreateVirtualDevice());
599         observers.put(R.string.pref_enable_client_native_ime, v -> recreateVirtualDevice());
600         observers.put(R.string.pref_enable_custom_home, v -> recreateVirtualDevice());
601 
602         return observers;
603     }
604 }
605