1 /* 2 * Copyright 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.applaunch; 18 19 import static android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE; 20 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 21 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 22 import static android.content.pm.PackageManager.DONT_KILL_APP; 23 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; 24 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; 25 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; 26 import static android.virtualdevice.cts.common.StreamedAppConstants.CUSTOM_HOME_ACTIVITY; 27 import static android.virtualdevice.cts.common.StreamedAppConstants.DEFAULT_HOME_ACTIVITY; 28 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.assumeNotNull; 34 import static org.junit.Assume.assumeTrue; 35 import static org.mockito.ArgumentMatchers.any; 36 import static org.mockito.ArgumentMatchers.anyInt; 37 import static org.mockito.ArgumentMatchers.eq; 38 import static org.mockito.Mockito.never; 39 import static org.mockito.Mockito.reset; 40 import static org.mockito.Mockito.timeout; 41 import static org.mockito.Mockito.verify; 42 43 import android.app.WallpaperManager; 44 import android.companion.virtual.VirtualDeviceManager; 45 import android.companion.virtual.VirtualDeviceParams; 46 import android.companion.virtual.flags.Flags; 47 import android.content.ComponentName; 48 import android.content.Context; 49 import android.content.Intent; 50 import android.content.pm.ActivityInfo; 51 import android.content.pm.PackageManager; 52 import android.content.pm.ResolveInfo; 53 import android.content.res.Resources; 54 import android.hardware.display.DisplayManager; 55 import android.hardware.display.VirtualDisplay; 56 import android.hardware.display.VirtualDisplayConfig; 57 import android.platform.test.annotations.AppModeFull; 58 import android.platform.test.annotations.RequiresFlagsEnabled; 59 import android.server.wm.WindowManagerState; 60 import android.virtualdevice.cts.applaunch.AppComponents.EmptyActivity; 61 import android.virtualdevice.cts.common.VirtualDeviceRule; 62 63 import androidx.test.ext.junit.runners.AndroidJUnit4; 64 65 import com.android.compatibility.common.util.ApiTest; 66 import com.android.compatibility.common.util.FeatureUtil; 67 import com.android.compatibility.common.util.SystemUtil; 68 69 import org.junit.Before; 70 import org.junit.Rule; 71 import org.junit.Test; 72 import org.junit.runner.RunWith; 73 import org.mockito.Mock; 74 import org.mockito.MockitoAnnotations; 75 76 import java.util.concurrent.TimeUnit; 77 78 /** 79 * Tests for home support on displays created by virtual devices. 80 */ 81 @RunWith(AndroidJUnit4.class) 82 @AppModeFull(reason = "VirtualDeviceManager cannot be accessed by instant apps") 83 @RequiresFlagsEnabled(Flags.FLAG_VDM_CUSTOM_HOME) 84 public class VirtualDeviceHomeTest { 85 86 private static final VirtualDisplayConfig.Builder HOME_DISPLAY_CONFIG = 87 VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder() 88 .setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 89 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 90 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 91 private static final VirtualDisplayConfig.Builder UNTRUSTED_HOME_DISPLAY_CONFIG = 92 VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder() 93 .setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 94 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 95 private static final VirtualDisplayConfig.Builder AUTO_MIRROR_HOME_DISPLAY_CONFIG = 96 VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder() 97 .setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR); 98 private static final VirtualDisplayConfig.Builder PUBLIC_HOME_DISPLAY_CONFIG = 99 VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder() 100 .setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC); 101 102 private static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(3); 103 104 @Rule 105 public VirtualDeviceRule mRule = VirtualDeviceRule.withAdditionalPermissions( 106 CHANGE_COMPONENT_ENABLED_STATE); 107 108 private final Context mContext = getInstrumentation().getContext(); 109 110 private VirtualDisplay mVirtualDisplay; 111 112 @Mock 113 private VirtualDeviceManager.ActivityListener mActivityListener; 114 115 @Before setUp()116 public void setUp() { 117 MockitoAnnotations.initMocks(this); 118 assumeTrue(FeatureUtil.hasSystemFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); 119 assumeTrue(isHomeSupportedOnVirtualDisplay()); 120 } 121 122 /** 123 * Home activities and wallpaper are not shown on untrusted displays. 124 */ 125 @ApiTest(apis = {"android.hardware.display.VirtualDisplayConfig.Builder#setHomeSupported"}) 126 @Test virtualDeviceHome_untrustedVirtualDisplay()127 public void virtualDeviceHome_untrustedVirtualDisplay() { 128 createVirtualDeviceAndHomeDisplay(UNTRUSTED_HOME_DISPLAY_CONFIG, /* homeComponent= */ null); 129 130 verify(mActivityListener, never()).onTopActivityChanged(anyInt(), any(), anyInt()); 131 assertThat(isWallpaperOnVirtualDisplay(mRule.getWmState())).isFalse(); 132 } 133 134 /** 135 * Home activities and wallpaper are not shown on auto-mirror displays. 136 */ 137 @ApiTest(apis = {"android.hardware.display.VirtualDisplayConfig.Builder#setHomeSupported"}) 138 @Test virtualDeviceHome_autoMirrorVirtualDisplay()139 public void virtualDeviceHome_autoMirrorVirtualDisplay() { 140 createVirtualDeviceAndHomeDisplay(AUTO_MIRROR_HOME_DISPLAY_CONFIG, 141 /* homeComponent= */ null); 142 143 verify(mActivityListener, never()).onTopActivityChanged(anyInt(), any(), anyInt()); 144 assertThat(isWallpaperOnVirtualDisplay(mRule.getWmState())).isFalse(); 145 } 146 147 /** 148 * Home activities and wallpaper are not shown on public displays without the flag 149 * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY. 150 */ 151 @ApiTest(apis = {"android.hardware.display.VirtualDisplayConfig.Builder#setHomeSupported"}) 152 @Test virtualDeviceHome_publicVirtualDisplay()153 public void virtualDeviceHome_publicVirtualDisplay() { 154 createVirtualDeviceAndHomeDisplay(PUBLIC_HOME_DISPLAY_CONFIG, /* homeComponent= */ null); 155 156 verify(mActivityListener, never()).onTopActivityChanged(anyInt(), any(), anyInt()); 157 assertThat(isWallpaperOnVirtualDisplay(mRule.getWmState())).isFalse(); 158 } 159 160 /** 161 * Wallpaper is shown on virtual displays that support home without a custom home component. 162 */ 163 @ApiTest(apis = {"android.hardware.display.VirtualDisplayConfig.Builder#setHomeSupported"}) 164 @Test virtualDeviceHome_noHomeComponent_showsWallpaper()165 public void virtualDeviceHome_noHomeComponent_showsWallpaper() { 166 assumeTrue(WallpaperManager.getInstance(mContext).isWallpaperSupported()); 167 try (HomeActivitySession ignored = new HomeActivitySession(DEFAULT_HOME_ACTIVITY)) { 168 createVirtualDeviceAndHomeDisplay(/* homeComponent= */ null); 169 assertThat(mRule.getWmState().waitForWithAmState( 170 this::isWallpaperOnVirtualDisplay, "Wallpaper is on virtual display")) 171 .isTrue(); 172 } 173 } 174 175 /** 176 * Wallpaper is shown on virtual displays that support home with a custom home component. 177 */ 178 @ApiTest(apis = {"android.hardware.display.VirtualDisplayConfig.Builder#setHomeSupported"}) 179 @Test virtualDeviceHome_withHomeComponent_showsWallpaper()180 public void virtualDeviceHome_withHomeComponent_showsWallpaper() { 181 assumeTrue(WallpaperManager.getInstance(mContext).isWallpaperSupported()); 182 createVirtualDeviceAndHomeDisplay(CUSTOM_HOME_ACTIVITY); 183 assertThat(mRule.getWmState().waitForWithAmState( 184 this::isWallpaperOnVirtualDisplay, "Wallpaper is on virtual display")) 185 .isTrue(); 186 } 187 188 /** 189 * The device-default secondary home activity is started on virtual displays that support home 190 * if the didn't specify a custom home component. That activity is at the hierarchy root. 191 */ 192 @ApiTest(apis = {"android.hardware.display.VirtualDisplayConfig.Builder#setHomeSupported"}) 193 @Test virtualDeviceHome_noCustomHomeComponent()194 public void virtualDeviceHome_noCustomHomeComponent() { 195 try (HomeActivitySession session = new HomeActivitySession(DEFAULT_HOME_ACTIVITY)) { 196 final ComponentName homeComponent = session.getCurrentSecondaryHomeComponent(); 197 createVirtualDeviceAndHomeDisplay(/* homeComponent= */ null); 198 assertActivityOnVirtualDisplay(homeComponent); 199 200 EmptyActivity activity = 201 mRule.startActivityOnDisplaySync(mVirtualDisplay, EmptyActivity.class); 202 assertActivityOnVirtualDisplay(activity.getComponentName()); 203 204 activity.finish(); 205 mRule.getWmState().waitAndAssertActivityRemoved(activity.getComponentName()); 206 207 assertActivityOnVirtualDisplay(homeComponent); 208 } 209 } 210 211 /** 212 * The device-default secondary home activity is started on virtual displays that support home 213 * if the didn't specify a custom home component. That activity is resolved when a home intent 214 * is sent to the relevant display. 215 */ 216 @ApiTest(apis = {"android.hardware.display.VirtualDisplayConfig.Builder#setHomeSupported"}) 217 @Test virtualDeviceHome_noCustomHomeComponent_sendHomeIntent()218 public void virtualDeviceHome_noCustomHomeComponent_sendHomeIntent() { 219 try (HomeActivitySession session = new HomeActivitySession(DEFAULT_HOME_ACTIVITY)) { 220 final ComponentName homeComponent = session.getCurrentSecondaryHomeComponent(); 221 createVirtualDeviceAndHomeDisplay(/* homeComponent= */ null); 222 assertActivityOnVirtualDisplay(homeComponent); 223 224 EmptyActivity activity = 225 mRule.startActivityOnDisplaySync(mVirtualDisplay, EmptyActivity.class); 226 assertActivityOnVirtualDisplay(activity.getComponentName()); 227 228 sendHomeIntentOnVirtualDisplay(); 229 assertActivityOnVirtualDisplay(homeComponent); 230 } 231 } 232 233 /** 234 * The device-default secondary home activity is started on virtual displays that support home 235 * if they specified an invalid custom home component. 236 */ 237 @ApiTest(apis = {"android.companion.virtual.VirtualDeviceParams.Builder#setHomeComponent"}) 238 @Test virtualDeviceHome_invalidCustomHomeComponent_fallbackToDefaultSecondaryHome()239 public void virtualDeviceHome_invalidCustomHomeComponent_fallbackToDefaultSecondaryHome() { 240 try (HomeActivitySession session = new HomeActivitySession(DEFAULT_HOME_ACTIVITY)) { 241 final ComponentName homeComponent = session.getCurrentSecondaryHomeComponent(); 242 createVirtualDeviceAndHomeDisplay(new ComponentName("foo.bar", "foo.bar.Baz")); 243 assertActivityOnVirtualDisplay(homeComponent); 244 } 245 } 246 247 /** 248 * The explicitly specified custom home activity is started on virtual displays that support 249 * home. That activity is at the hierarchy root. 250 */ 251 @ApiTest(apis = {"android.companion.virtual.VirtualDeviceParams.Builder#setHomeComponent"}) 252 @Test virtualDeviceHome_withCustomHomeComponent()253 public void virtualDeviceHome_withCustomHomeComponent() { 254 createVirtualDeviceAndHomeDisplay(CUSTOM_HOME_ACTIVITY); 255 assertActivityOnVirtualDisplay(CUSTOM_HOME_ACTIVITY); 256 257 EmptyActivity activity = 258 mRule.startActivityOnDisplaySync(mVirtualDisplay, EmptyActivity.class); 259 assertActivityOnVirtualDisplay(activity.getComponentName()); 260 261 activity.finish(); 262 mRule.getWmState().waitAndAssertActivityRemoved(activity.getComponentName()); 263 264 assertActivityOnVirtualDisplay(CUSTOM_HOME_ACTIVITY); 265 } 266 267 /** 268 * The explicitly specified custom home activity is started on virtual displays that support 269 * home. That activity is resolved when a home intent is sent to the relevant display. 270 */ 271 @ApiTest(apis = {"android.companion.virtual.VirtualDeviceParams.Builder#setHomeComponent"}) 272 @Test virtualDeviceHome_withCustomHomeComponent_sendHomeIntent()273 public void virtualDeviceHome_withCustomHomeComponent_sendHomeIntent() { 274 createVirtualDeviceAndHomeDisplay(CUSTOM_HOME_ACTIVITY); 275 assertActivityOnVirtualDisplay(CUSTOM_HOME_ACTIVITY); 276 277 EmptyActivity activity = 278 mRule.startActivityOnDisplaySync(mVirtualDisplay, EmptyActivity.class); 279 assertActivityOnVirtualDisplay(activity.getComponentName()); 280 281 sendHomeIntentOnVirtualDisplay(); 282 assertActivityOnVirtualDisplay(CUSTOM_HOME_ACTIVITY); 283 } 284 createVirtualDeviceAndHomeDisplay(ComponentName homeComponent)285 private void createVirtualDeviceAndHomeDisplay(ComponentName homeComponent) { 286 createVirtualDeviceAndHomeDisplay(HOME_DISPLAY_CONFIG, homeComponent); 287 } 288 createVirtualDeviceAndHomeDisplay( VirtualDisplayConfig.Builder virtualDisplayConfigBuilder, ComponentName homeComponent)289 private void createVirtualDeviceAndHomeDisplay( 290 VirtualDisplayConfig.Builder virtualDisplayConfigBuilder, ComponentName homeComponent) { 291 VirtualDeviceManager.VirtualDevice virtualDevice = mRule.createManagedVirtualDevice( 292 new VirtualDeviceParams.Builder().setHomeComponent(homeComponent).build()); 293 virtualDevice.addActivityListener(mContext.getMainExecutor(), mActivityListener); 294 mVirtualDisplay = mRule.createManagedVirtualDisplay( 295 virtualDevice, virtualDisplayConfigBuilder.setHomeSupported(true).build()); 296 } 297 sendHomeIntentOnVirtualDisplay()298 private void sendHomeIntentOnVirtualDisplay() { 299 Intent intent = new Intent(Intent.ACTION_MAIN); 300 intent.addCategory(Intent.CATEGORY_HOME); 301 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 302 mRule.sendIntentToDisplay(intent, mVirtualDisplay); 303 } 304 assertActivityOnVirtualDisplay(ComponentName componentName)305 private void assertActivityOnVirtualDisplay(ComponentName componentName) { 306 verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onTopActivityChanged( 307 eq(mVirtualDisplay.getDisplay().getDisplayId()), eq(componentName), 308 eq(mContext.getUserId())); 309 reset(mActivityListener); 310 } 311 isWallpaperOnVirtualDisplay(WindowManagerState state)312 private boolean isWallpaperOnVirtualDisplay(WindowManagerState state) { 313 return state.getMatchingWindowType(TYPE_WALLPAPER).stream().anyMatch( 314 w -> w.getDisplayId() == mVirtualDisplay.getDisplay().getDisplayId()); 315 } 316 isHomeSupportedOnVirtualDisplay()317 private boolean isHomeSupportedOnVirtualDisplay() { 318 try { 319 return mContext.getResources().getBoolean( 320 Resources.getSystem().getIdentifier( 321 "config_supportsSystemDecorsOnSecondaryDisplays", "bool", "android")); 322 } catch (Resources.NotFoundException e) { 323 // Assume this device support system decorations. 324 return true; 325 } 326 } 327 328 /** 329 * HomeActivitySession is used to replace the default home component, so that you can use 330 * your preferred home for testing within the session. The original default home will be 331 * restored automatically afterward. 332 */ 333 private class HomeActivitySession implements AutoCloseable { 334 private final PackageManager mPackageManager; 335 private final String mOrigHome; 336 private final ComponentName mSessionHome; 337 HomeActivitySession(ComponentName sessionHome)338 HomeActivitySession(ComponentName sessionHome) { 339 mSessionHome = sessionHome; 340 mPackageManager = mContext.getPackageManager(); 341 mOrigHome = getDefaultHomeComponent(); 342 343 mPackageManager.setComponentEnabledSetting(mSessionHome, 344 COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP); 345 setDefaultHome(mSessionHome); 346 } 347 348 @Override close()349 public void close() { 350 mPackageManager.setComponentEnabledSetting(mSessionHome, 351 COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP); 352 if (mOrigHome != null) { 353 setDefaultHome(mOrigHome); 354 } 355 } 356 setDefaultHome(ComponentName componentName)357 private void setDefaultHome(ComponentName componentName) { 358 setDefaultHome(componentName.flattenToString()); 359 } 360 setDefaultHome(String component)361 private void setDefaultHome(String component) { 362 SystemUtil.runShellCommand("cmd package set-home-activity --user " 363 + android.os.Process.myUserHandle().getIdentifier() + " " 364 + component); 365 } 366 getCurrentSecondaryHomeComponent()367 ComponentName getCurrentSecondaryHomeComponent() { 368 boolean useSystemProvidedLauncher = mContext.getResources().getBoolean( 369 Resources.getSystem().getIdentifier( 370 "config_useSystemProvidedLauncherForSecondary", 371 "bool", "android")); 372 return useSystemProvidedLauncher ? getDefaultSecondaryHomeComponent() : mSessionHome; 373 } 374 375 /** Fetches the component name of the default launcher. */ getDefaultHomeComponent()376 private String getDefaultHomeComponent() { 377 final String prefix = "Launcher: ComponentInfo{"; 378 final String postfix = "}"; 379 for (String s : SystemUtil.runShellCommand( 380 "cmd shortcut get-default-launcher").split("\n")) { 381 if (s.startsWith(prefix) && s.endsWith(postfix)) { 382 return s.substring(prefix.length(), s.length() - postfix.length()); 383 } 384 } 385 throw new AssertionError("No default launcher found"); 386 } 387 getDefaultSecondaryHomeComponent()388 private ComponentName getDefaultSecondaryHomeComponent() { 389 int resId = Resources.getSystem().getIdentifier( 390 "config_secondaryHomePackage", "string", "android"); 391 final Intent intent = new Intent(Intent.ACTION_MAIN); 392 intent.addCategory(Intent.CATEGORY_SECONDARY_HOME); 393 intent.setPackage(mContext.getResources().getString(resId)); 394 return resolveHomeIntent(intent); 395 } 396 resolveHomeIntent(Intent intent)397 private ComponentName resolveHomeIntent(Intent intent) { 398 final ResolveInfo resolveInfo = 399 mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY); 400 assumeNotNull(resolveInfo); 401 ActivityInfo activityInfo = resolveInfo.activityInfo; 402 return new ComponentName(activityInfo.packageName, activityInfo.name); 403 } 404 } 405 } 406