1 /* 2 * Copyright (C) 2014 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 package com.android.cts.launchertests; 17 18 import static org.junit.Assert.assertNotEquals; 19 20 import android.app.Instrumentation; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.ServiceConnection; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.LauncherActivityInfo; 30 import android.content.pm.LauncherApps; 31 import android.content.pm.PackageManager; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.os.Messenger; 40 import android.os.UserHandle; 41 import android.os.UserManager; 42 import android.test.AndroidTestCase; 43 44 import androidx.test.InstrumentationRegistry; 45 46 import java.util.List; 47 import java.util.concurrent.Semaphore; 48 import java.util.concurrent.TimeUnit; 49 50 /** 51 * Tests for LauncherApps service 52 */ 53 public class LauncherAppsTests extends AndroidTestCase { 54 55 public static final String SIMPLE_APP_PACKAGE = "com.android.cts.launcherapps.simpleapp"; 56 private static final String NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE = 57 "com.android.cts.nolaunchableactivityapp"; 58 private static final String NO_COMPONENT_APP_PACKAGE = 59 "com.android.cts.nocomponentapp"; 60 private static final String NO_PERMISSION_APP_PACKAGE = 61 "com.android.cts.nopermissionapp"; 62 63 private static final String SYNTHETIC_APP_DETAILS_ACTIVITY = "android.app.AppDetailsActivity"; 64 65 public static final String USER_EXTRA = "user_extra"; 66 public static final String PACKAGE_EXTRA = "package_extra"; 67 public static final String REPLY_EXTRA = "reply_extra"; 68 69 private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile"; 70 71 public static final int MSG_RESULT = 0; 72 public static final int MSG_CHECK_PACKAGE_ADDED = 1; 73 public static final int MSG_CHECK_PACKAGE_REMOVED = 2; 74 public static final int MSG_CHECK_PACKAGE_CHANGED = 3; 75 public static final int MSG_CHECK_NO_PACKAGE_ADDED = 4; 76 77 public static final int RESULT_PASS = 1; 78 public static final int RESULT_FAIL = 2; 79 public static final int RESULT_TIMEOUT = 3; 80 81 private LauncherApps mLauncherApps; 82 private UserHandle mUser; 83 private Instrumentation mInstrumentation; 84 private Messenger mService; 85 private Connection mConnection; 86 private Result mResult; 87 private Messenger mResultMessenger; 88 89 @Override 90 protected void setUp() throws Exception { 91 super.setUp(); 92 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 93 Bundle arguments = InstrumentationRegistry.getArguments(); 94 UserManager userManager = (UserManager) mInstrumentation.getContext().getSystemService( 95 Context.USER_SERVICE); 96 mUser = getUserHandleArgument(userManager, "testUser", arguments); 97 mLauncherApps = (LauncherApps) mInstrumentation.getContext().getSystemService( 98 Context.LAUNCHER_APPS_SERVICE); 99 100 final Intent intent = new Intent(); 101 intent.setComponent(new ComponentName("com.android.cts.launchertests.support", 102 "com.android.cts.launchertests.support.LauncherCallbackTestsService")); 103 104 mConnection = new Connection(); 105 mInstrumentation.getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 106 mConnection.waitForService(); 107 mResult = new Result(Looper.getMainLooper()); 108 mResultMessenger = new Messenger(mResult); 109 } 110 111 public void testGetActivitiesForUserFails() throws Exception { 112 expectSecurityException(() -> mLauncherApps.getActivityList(null, mUser), 113 "getActivities for non-profile user failed to throw exception"); 114 } 115 116 public void testSimpleAppInstalledForUser() throws Exception { 117 List<LauncherActivityInfo> activities = 118 mLauncherApps.getActivityList(null, mUser); 119 // Check simple app is there. 120 boolean foundSimpleApp = false; 121 for (LauncherActivityInfo activity : activities) { 122 if (activity.getComponentName().getPackageName().equals( 123 SIMPLE_APP_PACKAGE)) { 124 foundSimpleApp = true; 125 } 126 assertTrue(activity.getUser().equals(mUser)); 127 } 128 assertTrue(foundSimpleApp); 129 130 // Also make sure getApplicationInfo works too. 131 final ApplicationInfo ai = 132 mLauncherApps.getApplicationInfo(SIMPLE_APP_PACKAGE, /* flags= */ 0, mUser); 133 assertEquals(SIMPLE_APP_PACKAGE, ai.packageName); 134 assertEquals(mUser, UserHandle.getUserHandleForUid(ai.uid)); 135 } 136 137 public void testAccessPrimaryProfileFromManagedProfile() throws Exception { 138 assertTrue(mLauncherApps.getActivityList(null, mUser).isEmpty()); 139 140 expectNameNotFoundException( 141 () -> mLauncherApps.getApplicationInfo(SIMPLE_APP_PACKAGE, /* flags= */ 0, mUser), 142 "get applicationInfo failed to throw name not found exception"); 143 assertFalse(mLauncherApps.isPackageEnabled(SIMPLE_APP_PACKAGE, mUser)); 144 145 final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.android.com/")); 146 assertNull(mLauncherApps.resolveActivity(intent, mUser)); 147 } 148 149 public void testGetProfiles_fromMainProfile() { 150 final List<UserHandle> profiles = mLauncherApps.getProfiles(); 151 assertEquals(2, profiles.size()); 152 assertTrue(profiles.contains(android.os.Process.myUserHandle())); 153 assertEquals(getContext().getSystemService(UserManager.class).getUserProfiles(), 154 profiles); 155 } 156 157 public void testGetProfiles_fromManagedProfile() { 158 final List<UserHandle> profiles = mLauncherApps.getProfiles(); 159 assertEquals(1, profiles.size()); 160 assertEquals(android.os.Process.myUserHandle(), profiles.get(0)); 161 } 162 163 public void testPackageAddedCallbackForUser() throws Throwable { 164 int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_ADDED, 165 mUser, SIMPLE_APP_PACKAGE); 166 assertEquals(RESULT_PASS, result); 167 } 168 169 public void testPackageRemovedCallbackForUser() throws Throwable { 170 int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_REMOVED, 171 mUser, SIMPLE_APP_PACKAGE); 172 assertEquals(RESULT_PASS, result); 173 } 174 public void testPackageChangedCallbackForUser() throws Throwable { 175 int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_CHANGED, 176 mUser, SIMPLE_APP_PACKAGE); 177 assertEquals(RESULT_PASS, result); 178 } 179 180 public void testNoPackageAddedCallbackForUser() throws Throwable { 181 int result = sendMessageToCallbacksService(MSG_CHECK_NO_PACKAGE_ADDED, 182 mUser, SIMPLE_APP_PACKAGE); 183 assertEquals(RESULT_PASS, result); 184 } 185 186 public void testLaunchNonExportActivityFails() throws Exception { 187 expectSecurityException(() -> mLauncherApps.startMainActivity(new ComponentName( 188 SIMPLE_APP_PACKAGE, SIMPLE_APP_PACKAGE + ".NonExportedActivity"), 189 mUser, null, null), 190 "starting non-exported activity failed to throw exception"); 191 } 192 193 public void testLaunchNonExportLauncherFails() throws Exception { 194 expectSecurityException(() -> mLauncherApps.startMainActivity(new ComponentName( 195 SIMPLE_APP_PACKAGE, SIMPLE_APP_PACKAGE + ".NonLauncherActivity"), 196 mUser, null, null), 197 "starting non-launcher activity failed to throw exception"); 198 } 199 200 public void testLaunchMainActivity() throws Exception { 201 ActivityLaunchedReceiver receiver = new ActivityLaunchedReceiver(); 202 IntentFilter filter = new IntentFilter(); 203 filter.addAction(ActivityLaunchedReceiver.ACTIVITY_LAUNCHED_ACTION); 204 mInstrumentation.getContext().registerReceiver(receiver, filter); 205 mLauncherApps.startMainActivity(new ComponentName( 206 SIMPLE_APP_PACKAGE, 207 SIMPLE_APP_PACKAGE + ".SimpleActivity"), 208 mUser, null, null); 209 assertEquals(RESULT_PASS, receiver.waitForActivity()); 210 mInstrumentation.getContext().unregisterReceiver(receiver); 211 } 212 213 public void testReverseAccessNoThrow() throws Exception { 214 // Trying to access the main profile from a managed profile -> shouldn't throw but 215 // should just return false. 216 assertFalse(mLauncherApps.isPackageEnabled("android", mUser)); 217 } 218 219 public void testNoLaunchableActivityAppHasAppDetailsActivityInjected() throws Exception { 220 // NoLaunchableActivityApp is installed for duration of this test - make sure 221 // it's present on the activity list, has the synthetic activity generated, and it's 222 // enabled and exported 223 assertActivityInjected(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE); 224 } 225 226 public void testGetSetSyntheticAppDetailsActivityEnabled() throws Exception { 227 assertActivityInjected(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE); 228 PackageManager pm = mInstrumentation.getContext().getPackageManager(); 229 try { 230 pm.setSyntheticAppDetailsActivityEnabled(mContext.getPackageName(), false); 231 fail("Should not able to change current app's app details activity state"); 232 } catch (SecurityException e) { 233 // Expected: No permission 234 } 235 try { 236 pm.setSyntheticAppDetailsActivityEnabled(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE, false); 237 fail("Should not able to change other app's app details activity state"); 238 } catch (SecurityException e) { 239 // Expected: No permission 240 } 241 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(); 242 try { 243 assertTrue( 244 pm.getSyntheticAppDetailsActivityEnabled(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE)); 245 // Disable app details activity and assert if the change is applied 246 pm.setSyntheticAppDetailsActivityEnabled(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE, false); 247 assertFalse( 248 pm.getSyntheticAppDetailsActivityEnabled(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE)); 249 assertInjectedActivityNotFound(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE); 250 // Enable app details activity and assert if the change is applied 251 pm.setSyntheticAppDetailsActivityEnabled(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE, true); 252 assertTrue( 253 pm.getSyntheticAppDetailsActivityEnabled(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE)); 254 assertActivityInjected(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE); 255 } finally { 256 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 257 } 258 } 259 260 261 public void testProfileOwnerLauncherActivityInjected() throws Exception { 262 assertActivityInjected(MANAGED_PROFILE_PKG); 263 } 264 265 public void testNoComponentAppNotInjected() throws Exception { 266 // NoComponentApp is installed for duration of this test - make sure 267 // it's NOT present on the activity list 268 assertInjectedActivityNotFound(NO_COMPONENT_APP_PACKAGE); 269 } 270 271 public void testNoPermissionAppNotInjected() throws Exception { 272 // NoPermissionApp is installed for duration of this test - make sure 273 // it's NOT present on the activity list 274 assertInjectedActivityNotFound(NO_PERMISSION_APP_PACKAGE); 275 } 276 277 public void testDoPoNoTestAppInjectedActivityFound() throws Exception { 278 // NoLaunchableActivityApp is installed for duration of this test - make sure 279 // it's NOT present on the activity list For example, DO / PO mode won't show icons. 280 // This test is being called by DeviceOwnerTest. 281 assertInjectedActivityNotFound(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE); 282 } 283 284 public void testProfileOwnerInjectedActivityNotFound() throws Exception { 285 assertInjectedActivityNotFound(MANAGED_PROFILE_PKG); 286 } 287 288 public void testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception { 289 List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser); 290 for (LauncherActivityInfo activity : activities) { 291 if (!activity.getUser().equals(mUser)) { 292 continue; 293 } 294 ApplicationInfo appInfo = activity.getApplicationInfo(); 295 boolean isSystemApp = ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) 296 || ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); 297 if (isSystemApp) { 298 // make sure we haven't generated a synthetic app details activity for it 299 assertNotEquals("Found a system app that had a synthetic activity generated," 300 + " package name: " + activity.getComponentName().getPackageName() 301 + "; activity name: " + activity.getName(), 302 activity.getName(), 303 SYNTHETIC_APP_DETAILS_ACTIVITY); 304 } 305 } 306 } 307 308 private void expectSecurityException(ExceptionRunnable action, String failMessage) 309 throws Exception { 310 try { 311 action.run(); 312 fail(failMessage); 313 } catch (SecurityException e) { 314 // expected 315 } 316 } 317 318 private void expectNameNotFoundException(ExceptionRunnable action, String failMessage) 319 throws Exception { 320 try { 321 action.run(); 322 fail(failMessage); 323 } catch (PackageManager.NameNotFoundException e) { 324 // expected 325 } 326 } 327 328 private void assertActivityInjected(String targetPackage) { 329 List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser); 330 boolean noLaunchableActivityAppFound = false; 331 for (LauncherActivityInfo activity : activities) { 332 if (!activity.getUser().equals(mUser)) { 333 continue; 334 } 335 ComponentName compName = activity.getComponentName(); 336 if (compName.getPackageName().equals(targetPackage)) { 337 noLaunchableActivityAppFound = true; 338 // make sure it points to the synthetic app details activity 339 assertEquals(activity.getName(), SYNTHETIC_APP_DETAILS_ACTIVITY); 340 // make sure it's both exported and enabled 341 try { 342 PackageManager pm = mInstrumentation.getContext().getPackageManager(); 343 ActivityInfo ai = pm.getActivityInfo(compName, /*flags=*/ 0); 344 assertTrue("Component " + compName + " is not enabled", ai.enabled); 345 assertTrue("Component " + compName + " is not exported", ai.exported); 346 } catch (NameNotFoundException e) { 347 fail("Package " + compName.getPackageName() + " not found."); 348 } 349 } 350 } 351 assertTrue(noLaunchableActivityAppFound); 352 } 353 354 @FunctionalInterface 355 public interface ExceptionRunnable { 356 void run() throws Exception; 357 } 358 359 private UserHandle getUserHandleArgument(UserManager userManager, String key, 360 Bundle arguments) throws Exception { 361 String serial = arguments.getString(key); 362 if (serial == null) { 363 return null; 364 } 365 int serialNo = Integer.parseInt(serial); 366 return userManager.getUserForSerialNumber(serialNo); 367 } 368 369 private class Connection implements ServiceConnection { 370 private final Semaphore mSemaphore = new Semaphore(0); 371 372 @Override 373 public void onServiceConnected(ComponentName className, IBinder service) { 374 mService = new Messenger(service); 375 mSemaphore.release(); 376 } 377 378 @Override 379 public void onServiceDisconnected(ComponentName className) { 380 mService = null; 381 } 382 383 public void waitForService() { 384 try { 385 if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) { 386 return; 387 } 388 } catch (InterruptedException e) { 389 } 390 fail("failed to connec to service"); 391 } 392 }; 393 394 private static class Result extends Handler { 395 396 private final Semaphore mSemaphore = new Semaphore(0); 397 public int result = 0; 398 399 public Result(Looper looper) { 400 super(looper); 401 } 402 403 @Override 404 public void handleMessage(Message msg) { 405 if (msg.what == MSG_RESULT) { 406 result = msg.arg1; 407 mSemaphore.release(); 408 } else { 409 super.handleMessage(msg); 410 } 411 } 412 413 public int waitForResult() { 414 try { 415 if (mSemaphore.tryAcquire(120, TimeUnit.SECONDS)) { 416 return result; 417 } 418 } catch (InterruptedException e) { 419 } 420 return RESULT_TIMEOUT; 421 } 422 } 423 424 public class ActivityLaunchedReceiver extends BroadcastReceiver { 425 public static final String ACTIVITY_LAUNCHED_ACTION = 426 "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION"; 427 428 private final Semaphore mSemaphore = new Semaphore(0); 429 430 @Override 431 public void onReceive(Context context, Intent intent) { 432 if (intent.getAction().equals(ACTIVITY_LAUNCHED_ACTION)) { 433 mSemaphore.release(); 434 } 435 } 436 437 public int waitForActivity() { 438 try { 439 if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) { 440 return RESULT_PASS; 441 } 442 } catch (InterruptedException e) { 443 } 444 return RESULT_TIMEOUT; 445 } 446 } 447 448 private int sendMessageToCallbacksService(int msg, UserHandle user, String packageName) 449 throws Throwable { 450 Bundle params = new Bundle(); 451 params.putParcelable(USER_EXTRA, user); 452 params.putString(PACKAGE_EXTRA, packageName); 453 454 Message message = Message.obtain(null, msg, params); 455 message.replyTo = mResultMessenger; 456 457 mService.send(message); 458 459 return mResult.waitForResult(); 460 } 461 462 private void assertInjectedActivityNotFound(String targetPackage) { 463 List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser); 464 for (LauncherActivityInfo activity : activities) { 465 if (!activity.getUser().equals(mUser)) { 466 continue; 467 } 468 ComponentName compName = activity.getComponentName(); 469 if (compName.getPackageName().equals(targetPackage)) { 470 fail("Injected activity found: " + compName.flattenToString()); 471 } 472 } 473 } 474 } 475