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