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.virtualdevice.cts.common;
18 
19 import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
20 import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
21 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
22 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
23 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
24 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
25 import static android.graphics.ImageFormat.YUV_420_888;
26 import static android.hardware.camera2.CameraMetadata.LENS_FACING_BACK;
27 
28 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
29 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
30 
31 import static com.google.common.truth.Truth.assertThat;
32 
33 import static org.junit.Assume.assumeFalse;
34 import static org.junit.Assume.assumeNotNull;
35 import static org.junit.Assume.assumeTrue;
36 
37 import android.annotation.TargetApi;
38 import android.app.Activity;
39 import android.app.ActivityOptions;
40 import android.app.UiAutomation;
41 import android.companion.AssociationInfo;
42 import android.companion.virtual.VirtualDeviceManager;
43 import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
44 import android.companion.virtual.VirtualDeviceParams;
45 import android.companion.virtual.camera.VirtualCamera;
46 import android.companion.virtual.camera.VirtualCameraCallback;
47 import android.companion.virtual.camera.VirtualCameraConfig;
48 import android.content.ComponentName;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.graphics.SurfaceTexture;
52 import android.hardware.display.DisplayManager;
53 import android.hardware.display.VirtualDisplay;
54 import android.hardware.display.VirtualDisplayConfig;
55 import android.os.Bundle;
56 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
57 import android.server.wm.Condition;
58 import android.server.wm.WindowManagerState;
59 import android.server.wm.WindowManagerStateHelper;
60 import android.view.Display;
61 import android.view.Surface;
62 
63 import androidx.annotation.NonNull;
64 import androidx.annotation.Nullable;
65 import androidx.core.os.BuildCompat;
66 
67 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
68 import com.android.compatibility.common.util.FeatureUtil;
69 
70 import org.junit.rules.ExternalResource;
71 import org.junit.rules.RuleChain;
72 import org.junit.rules.TestRule;
73 import org.junit.runner.Description;
74 import org.junit.runners.model.Statement;
75 import org.mockito.Mock;
76 import org.mockito.MockitoAnnotations;
77 
78 import java.util.ArrayList;
79 import java.util.Arrays;
80 import java.util.Set;
81 import java.util.function.BooleanSupplier;
82 import java.util.function.Supplier;
83 import java.util.stream.Stream;
84 
85 /**
86  * A test rule that allows for testing VDM and virtual device features.
87  */
88 @TargetApi(34)
89 public class VirtualDeviceRule implements TestRule {
90 
91     /** General permissions needed for created virtual devices and displays. */
92     private static final String[] REQUIRED_PERMISSIONS = new String[] {
93             CREATE_VIRTUAL_DEVICE,
94             ADD_TRUSTED_DISPLAY
95     };
96 
97     public static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS =
98             new VirtualDeviceParams.Builder().build();
99     public static final VirtualDisplayConfig DEFAULT_VIRTUAL_DISPLAY_CONFIG =
100             createDefaultVirtualDisplayConfigBuilder().build();
101     public static final VirtualDisplayConfig TRUSTED_VIRTUAL_DISPLAY_CONFIG =
102             createDefaultVirtualDisplayConfigBuilder()
103                     .setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
104                             | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
105                             | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY)
106                     .build();
107 
108     public static final String DEFAULT_VIRTUAL_DISPLAY_NAME = "testVirtualDisplay";
109     public static final int DEFAULT_VIRTUAL_DISPLAY_WIDTH = 640;
110     public static final int DEFAULT_VIRTUAL_DISPLAY_HEIGHT = 480;
111     public static final int DEFAULT_VIRTUAL_DISPLAY_DPI = 240;
112 
113     public static final ComponentName BLOCKED_ACTIVITY_COMPONENT =
114             new ComponentName("android", "com.android.internal.app.BlockedAppStreamingActivity");
115 
116     private RuleChain mRuleChain;
117     private final FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
118     private final VirtualDeviceTrackerRule mTrackerRule = new VirtualDeviceTrackerRule();
119 
120     private final Context mContext = getInstrumentation().getTargetContext();
121     private final VirtualDeviceManager mVirtualDeviceManager =
122             mContext.getSystemService(VirtualDeviceManager.class);
123     private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
124 
125     /** Creates a rule with the required permissions for creating virtual devices and displays. */
createDefault()126     public static VirtualDeviceRule createDefault() {
127         return new VirtualDeviceRule(REQUIRED_PERMISSIONS);
128     }
129 
130     /** Creates a rule with any additional permission needed for the specific test. */
withAdditionalPermissions(String... additionalPermissions)131     public static VirtualDeviceRule withAdditionalPermissions(String... additionalPermissions) {
132         return new VirtualDeviceRule(Stream.concat(
133                 Arrays.stream(REQUIRED_PERMISSIONS), Arrays.stream(additionalPermissions))
134                 .toArray(String[]::new));
135     }
136 
VirtualDeviceRule(String... permissions)137     private VirtualDeviceRule(String... permissions) {
138         mRuleChain = RuleChain
139                 .outerRule(mFakeAssociationRule)
140                 .around(DeviceFlagsValueProvider.createCheckFlagsRule())
141                 .around(new AdoptShellPermissionsRule(
142                         getInstrumentation().getUiAutomation(), permissions))
143                 .around(mTrackerRule);
144     }
145 
146     /** Creates a rule with virtual camera support check before test execution. */
withVirtualCameraSupportCheck()147     public VirtualDeviceRule withVirtualCameraSupportCheck() {
148         mRuleChain = mRuleChain.around(new VirtualCameraSupportRule(this));
149         return this;
150     }
151 
152     @Override
apply(final Statement base, final Description description)153     public Statement apply(final Statement base, final Description description) {
154         assumeNotNull(mVirtualDeviceManager);
155         return mRuleChain.apply(base, description);
156     }
157 
158     /**
159      * Returns the VirtualDevice object for the given deviceId
160      */
getVirtualDevice(int deviceId)161     public android.companion.virtual.VirtualDevice getVirtualDevice(int deviceId) {
162         if (BuildCompat.isAtLeastV()) {
163             return mVirtualDeviceManager.getVirtualDevice(deviceId);
164         } else {
165             return mVirtualDeviceManager.getVirtualDevices().stream()
166                     .filter(device -> device.getDeviceId() == deviceId).findFirst().orElse(null);
167         }
168     }
169 
170     /**
171      * Creates a virtual device with default params that will be automatically closed when the
172      * test is torn down.
173      */
174     @NonNull
createManagedVirtualDevice()175     public VirtualDevice createManagedVirtualDevice() {
176         return createManagedVirtualDevice(DEFAULT_VIRTUAL_DEVICE_PARAMS);
177     }
178 
179     /**
180      * Creates a virtual device with the given params that will be automatically closed when the
181      * test is torn down.
182      */
183     @NonNull
createManagedVirtualDevice(@onNull VirtualDeviceParams params)184     public VirtualDevice createManagedVirtualDevice(@NonNull VirtualDeviceParams params) {
185         final VirtualDevice virtualDevice = mVirtualDeviceManager.createVirtualDevice(
186                 mFakeAssociationRule.getAssociationInfo().getId(), params);
187         mTrackerRule.mVirtualDevices.add(virtualDevice);
188         return virtualDevice;
189     }
190 
191     /**
192      * Creates a virtual display associated with the given device that will be automatically
193      * released when the test is torn down.
194      */
195     @Nullable
createManagedVirtualDisplay(@onNull VirtualDevice virtualDevice)196     public VirtualDisplay createManagedVirtualDisplay(@NonNull VirtualDevice virtualDevice) {
197         return createManagedVirtualDisplay(virtualDevice, DEFAULT_VIRTUAL_DISPLAY_CONFIG);
198     }
199 
200     /**
201      * Creates a virtual display associated with the given device and flags that will be
202      * automatically released when the test is torn down.
203      */
204     @Nullable
createManagedVirtualDisplayWithFlags( @onNull VirtualDevice virtualDevice, int flags)205     public VirtualDisplay createManagedVirtualDisplayWithFlags(
206             @NonNull VirtualDevice virtualDevice, int flags) {
207         return createManagedVirtualDisplay(virtualDevice,
208                 createDefaultVirtualDisplayConfigBuilder().setFlags(flags).build());
209     }
210 
211     /**
212      * Creates a virtual display associated with the given device and config that will be
213      * automatically released when the test is torn down.
214      */
215     @Nullable
createManagedVirtualDisplay(@onNull VirtualDevice virtualDevice, @NonNull VirtualDisplayConfig config)216     public VirtualDisplay createManagedVirtualDisplay(@NonNull VirtualDevice virtualDevice,
217             @NonNull VirtualDisplayConfig config) {
218         final VirtualDisplay virtualDisplay = virtualDevice.createVirtualDisplay(
219                 config, /* executor= */ null, /* callback= */ null);
220         if (virtualDisplay != null) {
221             assertDisplayExists(virtualDisplay.getDisplay().getDisplayId());
222             // There's no need to track managed virtual displays to have them released on tear-down
223             // because they will be released automatically when the VirtualDevice is closed.
224         }
225         return virtualDisplay;
226     }
227 
228     /**
229      * Creates a virtual display not associated with the any virtual device that will be
230      * automatically released when the test is torn down.
231      */
232     @Nullable
createManagedUnownedVirtualDisplay()233     public VirtualDisplay createManagedUnownedVirtualDisplay() {
234         return createManagedUnownedVirtualDisplay(DEFAULT_VIRTUAL_DISPLAY_CONFIG);
235     }
236 
237     /**
238      * Creates a virtual display not associated with the any virtual device with the given flags
239      * that will be automatically released when the test is torn down.
240      */
241     @Nullable
createManagedUnownedVirtualDisplayWithFlags(int flags)242     public VirtualDisplay createManagedUnownedVirtualDisplayWithFlags(int flags) {
243         return createManagedUnownedVirtualDisplay(
244                 createDefaultVirtualDisplayConfigBuilder().setFlags(flags).build());
245     }
246 
247     /**
248      * Creates a virtual display not associated with the any virtual device with the given config
249      * that will be automatically released when the test is torn down.
250      */
251     @Nullable
createManagedUnownedVirtualDisplay(@onNull VirtualDisplayConfig config)252     public VirtualDisplay createManagedUnownedVirtualDisplay(@NonNull VirtualDisplayConfig config) {
253         final VirtualDisplay virtualDisplay =
254                 mContext.getSystemService(DisplayManager.class).createVirtualDisplay(config);
255         if (virtualDisplay != null) {
256             assertDisplayExists(virtualDisplay.getDisplay().getDisplayId());
257             mTrackerRule.mVirtualDisplays.add(virtualDisplay);
258         }
259         return virtualDisplay;
260     }
261 
262     /**
263      * Default config for virtual display creation, with a predefined name, dimensions and an empty
264      * surface.
265      */
266     @NonNull
createDefaultVirtualDisplayConfigBuilder()267     public static VirtualDisplayConfig.Builder createDefaultVirtualDisplayConfigBuilder() {
268         return createDefaultVirtualDisplayConfigBuilder(
269                 DEFAULT_VIRTUAL_DISPLAY_WIDTH, DEFAULT_VIRTUAL_DISPLAY_HEIGHT);
270     }
271 
272     /**
273      * Default config for virtual display creation with custom dimensions.
274      */
275     @NonNull
createDefaultVirtualDisplayConfigBuilder( int width, int height)276     public static VirtualDisplayConfig.Builder createDefaultVirtualDisplayConfigBuilder(
277             int width, int height) {
278         // VirtualDevice#close will cause this to be recycled.
279         //noinspection Recycle
280         SurfaceTexture texture = new SurfaceTexture(1);
281         texture.setDefaultBufferSize(width, height);
282         return new VirtualDisplayConfig.Builder(
283                 DEFAULT_VIRTUAL_DISPLAY_NAME, width, height,  DEFAULT_VIRTUAL_DISPLAY_DPI)
284                 .setSurface(new Surface(texture));
285     }
286 
287     /**
288      * Blocks until the display with the given ID is available.
289      */
assertDisplayExists(int displayId)290     public void assertDisplayExists(int displayId) {
291         waitAndAssertWindowManagerState("Waiting for display to be available",
292                 () -> mWmState.getDisplay(displayId) != null);
293     }
294 
295     /**
296      * Blocks until the display with the given ID is removed.
297      */
assertDisplayDoesNotExist(int displayId)298     public void assertDisplayDoesNotExist(int displayId) {
299         waitAndAssertWindowManagerState("Waiting for display to be removed",
300                 () -> mWmState.getDisplay(displayId) == null);
301     }
302 
303     /** Returns the WM state helper. */
getWmState()304     public WindowManagerStateHelper getWmState() {
305         return mWmState;
306     }
307 
308     /** Creates a new CDM association. */
createManagedAssociation()309     public AssociationInfo createManagedAssociation() {
310         return mFakeAssociationRule.createManagedAssociation();
311     }
312 
313     /** Drops the current CDM association. */
dropCompanionDeviceAssociation()314     public void dropCompanionDeviceAssociation() {
315         mFakeAssociationRule.disassociate();
316     }
317 
318     /**
319      * Temporarily assumes the given permissions and executes the given supplier. Reverts any
320      * permissions currently held after the execution.
321      */
runWithTemporaryPermission(Supplier<T> supplier, String... permissions)322     public <T> T runWithTemporaryPermission(Supplier<T> supplier, String... permissions) {
323         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
324         final Set<String> currentPermissions = uiAutomation.getAdoptedShellPermissions();
325         uiAutomation.adoptShellPermissionIdentity(permissions);
326         try {
327             return supplier.get();
328         } finally {
329             // Revert the permissions needed for the test again.
330             uiAutomation.adoptShellPermissionIdentity(
331                     currentPermissions.toArray(new String[0]));
332         }
333     }
334 
335     /**
336      * Temporarily drops any permissions and executes the given supplier. Reverts any permissions
337      * currently held after the execution.
338      */
runWithoutPermissions(Supplier<T> supplier)339     public <T> T runWithoutPermissions(Supplier<T> supplier) {
340         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
341         final Set<String> currentPermissions = uiAutomation.getAdoptedShellPermissions();
342         uiAutomation.dropShellPermissionIdentity();
343         try {
344             return supplier.get();
345         } finally {
346             // Revert the permissions needed for the test again.
347             uiAutomation.adoptShellPermissionIdentity(
348                     currentPermissions.toArray(new String[0]));
349         }
350     }
351 
352     /**
353      * Starts the activity for the given class on the given virtual display and blocks until it is
354      * successfully launched there.
355      */
startActivityOnDisplaySync( VirtualDisplay virtualDisplay, Class<T> clazz)356     public <T extends Activity> T startActivityOnDisplaySync(
357             VirtualDisplay virtualDisplay, Class<T> clazz) {
358         final int displayId = virtualDisplay.getDisplay().getDisplayId();
359         return startActivityOnDisplaySync(displayId, clazz);
360     }
361 
362     /**
363      * Sends the given intent to the given virtual display.
364      */
sendIntentToDisplay(Intent intent, VirtualDisplay virtualDisplay)365     public void sendIntentToDisplay(Intent intent, VirtualDisplay virtualDisplay) {
366         sendIntentToDisplay(intent, virtualDisplay.getDisplay().getDisplayId());
367     }
368 
369     /**
370      * Sends the given intent to the given display.
371      */
sendIntentToDisplay(Intent intent, int displayId)372     public void sendIntentToDisplay(Intent intent, int displayId) {
373         assumeActivityLaunchSupported(displayId);
374         mContext.startActivity(intent, createActivityOptions(displayId));
375     }
376 
377     /**
378      * Starts the activity for the given class on the given display and blocks until it is
379      * successfully launched there.
380      */
startActivityOnDisplaySync(int displayId, Class<T> clazz)381     public <T extends Activity> T startActivityOnDisplaySync(int displayId, Class<T> clazz) {
382         return startActivityOnDisplaySync(displayId, new Intent(mContext, clazz)
383                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
384     }
385 
386     /**
387      * Starts the activity for the given intent on the given virtual display and blocks until it is
388      * successfully launched there.
389      */
startActivityOnDisplaySync( VirtualDisplay virtualDisplay, Intent intent)390     public <T extends Activity> T startActivityOnDisplaySync(
391             VirtualDisplay virtualDisplay, Intent intent) {
392         return startActivityOnDisplaySync(virtualDisplay.getDisplay().getDisplayId(), intent);
393     }
394 
395     /**
396      * Starts the activity for the given intent on the given display and blocks until it is
397      * successfully launched there.
398      */
startActivityOnDisplaySync(int displayId, Intent intent)399     public <T extends Activity> T startActivityOnDisplaySync(int displayId, Intent intent) {
400         assumeActivityLaunchSupported(displayId);
401         return (T) getInstrumentation().startActivitySync(intent, createActivityOptions(displayId));
402     }
403 
404     /**
405      * Creates activity options for launching activities on the given virtual display.
406      */
createActivityOptions(VirtualDisplay virtualDisplay)407     public static Bundle createActivityOptions(VirtualDisplay virtualDisplay) {
408         return createActivityOptions(virtualDisplay.getDisplay().getDisplayId());
409     }
410 
411     /**
412      * Creates activity options for launching activities on the given display.
413      */
createActivityOptions(int displayId)414     public static Bundle createActivityOptions(int displayId) {
415         return ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
416     }
417 
418     /**
419      * Skips the test if the device doesn't support virtual displays that can host activities.
420      */
assumeActivityLaunchSupported(int displayId)421     public void assumeActivityLaunchSupported(int displayId) {
422         if (displayId != Display.DEFAULT_DISPLAY) {
423             assumeTrue(FeatureUtil.hasSystemFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS));
424             // TODO(b/261155110): Re-enable once freeform mode is supported on virtual displays.
425             assumeFalse(FeatureUtil.hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT));
426         }
427     }
428 
429     /**
430      * Blocks until the given activity is in resumed state.
431      */
waitAndAssertActivityResumed(ComponentName componentName)432     public void waitAndAssertActivityResumed(ComponentName componentName) {
433         waitAndAssertWindowManagerState("Waiting for activity to be resumed",
434                 () -> mWmState.hasActivityState(componentName, WindowManagerState.STATE_RESUMED));
435     }
436 
437     /**
438      * Blocks until the given activity is gone.
439      */
waitAndAssertActivityRemoved(ComponentName componentName)440     public void waitAndAssertActivityRemoved(ComponentName componentName) {
441         waitAndAssertWindowManagerState("Waiting for activity to be removed",
442                 () -> !mWmState.containsActivity(componentName));
443     }
444 
445     /**
446      * Override the default retry limit of WindowManagerStateHelper.
447      * Destroying activities on virtual displays and destroying the virtual displays themselves
448      * takes longer than the default timeout of 5s.
449      */
waitAndAssertWindowManagerState( String message, BooleanSupplier waitCondition)450     private void waitAndAssertWindowManagerState(
451             String message, BooleanSupplier waitCondition) {
452         final Condition<String> condition =
453                 new Condition<>(message, () -> {
454                     mWmState.computeState();
455                     return waitCondition.getAsBoolean();
456                 });
457         condition.setRetryLimit(10);
458         assertThat(Condition.waitFor(condition)).isTrue();
459     }
460 
461     /**
462      * Internal rule that tracks all created virtual devices and displays and ensures they are
463      * properly closed and released after the test.
464      */
465     private static final class VirtualDeviceTrackerRule extends ExternalResource {
466 
467         final ArrayList<VirtualDevice> mVirtualDevices = new ArrayList<>();
468         final ArrayList<VirtualDisplay> mVirtualDisplays = new ArrayList<>();
469 
470         @Override
after()471         protected void after() {
472             for (VirtualDevice virtualDevice : mVirtualDevices) {
473                 virtualDevice.close();
474             }
475             mVirtualDevices.clear();
476             for (VirtualDisplay virtualDisplay : mVirtualDisplays) {
477                 virtualDisplay.release();
478             }
479             mVirtualDisplays.clear();
480             super.after();
481         }
482     }
483 
484     /**
485      * Internal rule that checks whether virtual camera is supported by the device, before executing
486      * any test.
487      */
488     private static final class VirtualCameraSupportRule extends ExternalResource {
489         private static final int VIRTUAL_CAMERA_SUPPORT_UNKNOWN = 0;
490         private static final int VIRTUAL_CAMERA_SUPPORT_AVAILABLE = 1;
491         private static final int VIRTUAL_CAMERA_SUPPORT_NOT_AVAILABLE = 2;
492 
493         @Mock
494         private VirtualCameraCallback mVirtualCameraCallback;
495 
496         private final VirtualDeviceRule mVirtualDeviceRule;
497         private int mVirtualCameraSupport = VIRTUAL_CAMERA_SUPPORT_UNKNOWN;
498 
VirtualCameraSupportRule(VirtualDeviceRule virtualDeviceRule)499         private VirtualCameraSupportRule(VirtualDeviceRule virtualDeviceRule) {
500             mVirtualDeviceRule = virtualDeviceRule;
501         }
502 
503         @Override
before()504         protected void before() {
505             MockitoAnnotations.initMocks(this);
506             assumeTrue("Virtual camera not available on this device",
507                     getVirtualCameraSupport() == VIRTUAL_CAMERA_SUPPORT_AVAILABLE);
508         }
509 
getVirtualCameraSupport()510         private int getVirtualCameraSupport() {
511             if (mVirtualCameraSupport != VIRTUAL_CAMERA_SUPPORT_UNKNOWN) {
512                 return mVirtualCameraSupport;
513             }
514 
515             try (VirtualDevice virtualDevice = mVirtualDeviceRule.createManagedVirtualDevice(
516                     new VirtualDeviceParams.Builder().setDevicePolicy(POLICY_TYPE_CAMERA,
517                             DEVICE_POLICY_CUSTOM).build())) {
518                 VirtualCameraConfig config = new VirtualCameraConfig.Builder("dummycam")
519                         .setVirtualCameraCallback(getApplicationContext().getMainExecutor(),
520                                 mVirtualCameraCallback)
521                         .addStreamConfig(640, 480, YUV_420_888, 30)
522                         .setLensFacing(LENS_FACING_BACK)
523                         .build();
524                 try (VirtualCamera ignored = virtualDevice.createVirtualCamera(config)) {
525                     mVirtualCameraSupport = VIRTUAL_CAMERA_SUPPORT_AVAILABLE;
526                 } catch (UnsupportedOperationException e) {
527                     mVirtualCameraSupport = VIRTUAL_CAMERA_SUPPORT_NOT_AVAILABLE;
528                 }
529             }
530             return mVirtualCameraSupport;
531         }
532     }
533 }
534