1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.L; 4 import static android.os.Build.VERSION_CODES.M; 5 import static android.os.Build.VERSION_CODES.N; 6 import static android.os.Build.VERSION_CODES.O; 7 import static android.os.Build.VERSION_CODES.O_MR1; 8 import static android.os.Build.VERSION_CODES.P; 9 import static android.os.Build.VERSION_CODES.R; 10 import static android.os.Build.VERSION_CODES.TIRAMISU; 11 import static com.google.common.truth.Truth.assertThat; 12 import static org.junit.Assert.assertEquals; 13 import static org.junit.Assert.assertThrows; 14 import static org.junit.Assert.assertTrue; 15 import static org.robolectric.Shadows.shadowOf; 16 import static org.robolectric.util.reflector.Reflector.reflector; 17 18 import android.content.ComponentName; 19 import android.content.Context; 20 import android.content.pm.ActivityInfo; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.IncrementalStatesInfo; 23 import android.content.pm.LauncherActivityInfo; 24 import android.content.pm.LauncherActivityInfoInternal; 25 import android.content.pm.LauncherApps; 26 import android.content.pm.LauncherApps.ShortcutQuery; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.pm.ResolveInfo; 29 import android.content.pm.ShortcutInfo; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.Looper; 34 import android.os.Process; 35 import android.os.UserHandle; 36 import androidx.test.core.app.ApplicationProvider; 37 import androidx.test.ext.junit.runners.AndroidJUnit4; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.concurrent.CountDownLatch; 41 import java.util.concurrent.TimeUnit; 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.robolectric.RuntimeEnvironment; 46 import org.robolectric.annotation.Config; 47 import org.robolectric.shadow.api.Shadow; 48 import org.robolectric.util.ReflectionHelpers; 49 import org.robolectric.util.ReflectionHelpers.ClassParameter; 50 import org.robolectric.util.reflector.Accessor; 51 import org.robolectric.util.reflector.ForType; 52 53 /** Robolectric test for {@link ShadowLauncherApps}. */ 54 @RunWith(AndroidJUnit4.class) 55 @Config(minSdk = O_MR1) 56 public class ShadowLauncherAppsTest { 57 private static final String TEST_PACKAGE_NAME = "test-package"; 58 private static final String TEST_PACKAGE_NAME_2 = "test-package2"; 59 private static final String TEST_PACKAGE_NAME_3 = "test-package3"; 60 private static final UserHandle USER_HANDLE = UserHandle.CURRENT; 61 private LauncherApps launcherApps; 62 63 private static class DefaultCallback extends LauncherApps.Callback { 64 @Override onPackageRemoved(String packageName, UserHandle user)65 public void onPackageRemoved(String packageName, UserHandle user) {} 66 67 @Override onPackageAdded(String packageName, UserHandle user)68 public void onPackageAdded(String packageName, UserHandle user) {} 69 70 @Override onPackageChanged(String packageName, UserHandle user)71 public void onPackageChanged(String packageName, UserHandle user) {} 72 73 @Override onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)74 public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {} 75 76 @Override onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)77 public void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing) {} 78 } 79 80 @Before setup()81 public void setup() { 82 launcherApps = 83 (LauncherApps) 84 ApplicationProvider.getApplicationContext() 85 .getSystemService(Context.LAUNCHER_APPS_SERVICE); 86 } 87 88 @ForType(ShortcutInfo.class) 89 private interface ReflectorShortcutInfo { 90 @Accessor("mPackageName") setPackage(String packageName)91 void setPackage(String packageName); 92 } 93 shadowLooper(Looper looper)94 private ShadowLooper shadowLooper(Looper looper) { 95 return Shadow.extract(looper); 96 } 97 98 @Test testIsPackageEnabled()99 public void testIsPackageEnabled() { 100 assertThat(launcherApps.isPackageEnabled(TEST_PACKAGE_NAME, USER_HANDLE)).isFalse(); 101 102 shadowOf(launcherApps).addEnabledPackage(USER_HANDLE, TEST_PACKAGE_NAME); 103 assertThat(launcherApps.isPackageEnabled(TEST_PACKAGE_NAME, USER_HANDLE)).isTrue(); 104 } 105 106 @Test 107 @Config(minSdk = O) getShortcutConfigActivityList_getsShortcutsForPackageName()108 public void getShortcutConfigActivityList_getsShortcutsForPackageName() { 109 LauncherActivityInfo launcherActivityInfo1 = 110 createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE); 111 LauncherActivityInfo launcherActivityInfo2 = 112 createLauncherActivityInfo(TEST_PACKAGE_NAME_2, USER_HANDLE); 113 shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1); 114 shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo2); 115 116 assertThat(launcherApps.getShortcutConfigActivityList(TEST_PACKAGE_NAME, USER_HANDLE)) 117 .contains(launcherActivityInfo1); 118 } 119 120 @Test 121 @Config(minSdk = O) getShortcutConfigActivityList_getsShortcutsForUserHandle()122 public void getShortcutConfigActivityList_getsShortcutsForUserHandle() { 123 LauncherActivityInfo launcherActivityInfo1 = 124 createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE); 125 LauncherActivityInfo launcherActivityInfo2 = 126 createLauncherActivityInfo(TEST_PACKAGE_NAME, UserHandle.of(10)); 127 shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1); 128 shadowOf(launcherApps).addShortcutConfigActivity(UserHandle.of(10), launcherActivityInfo2); 129 130 assertThat(launcherApps.getShortcutConfigActivityList(TEST_PACKAGE_NAME, UserHandle.of(10))) 131 .contains(launcherActivityInfo2); 132 } 133 134 @Test 135 @Config(minSdk = O) getShortcutConfigActivityList_packageNull_getsShortcutFromAllPackagesForUser()136 public void getShortcutConfigActivityList_packageNull_getsShortcutFromAllPackagesForUser() { 137 LauncherActivityInfo launcherActivityInfo1 = 138 createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE); 139 LauncherActivityInfo launcherActivityInfo2 = 140 createLauncherActivityInfo(TEST_PACKAGE_NAME_2, USER_HANDLE); 141 LauncherActivityInfo launcherActivityInfo3 = 142 createLauncherActivityInfo(TEST_PACKAGE_NAME_3, UserHandle.of(10)); 143 shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1); 144 shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo2); 145 shadowOf(launcherApps).addShortcutConfigActivity(UserHandle.of(10), launcherActivityInfo3); 146 147 assertThat(launcherApps.getShortcutConfigActivityList(null, USER_HANDLE)) 148 .containsExactly(launcherActivityInfo1, launcherActivityInfo2); 149 } 150 151 @Test 152 @Config(minSdk = L, maxSdk = M) testGetActivityListPreN()153 public void testGetActivityListPreN() { 154 assertThat(launcherApps.getActivityList(TEST_PACKAGE_NAME, USER_HANDLE)).isEmpty(); 155 156 ResolveInfo info = 157 ShadowResolveInfo.newResolveInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, TEST_PACKAGE_NAME); 158 LauncherActivityInfo launcherActivityInfo = 159 ReflectionHelpers.callConstructor( 160 LauncherActivityInfo.class, 161 ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()), 162 ClassParameter.from(ResolveInfo.class, info), 163 ClassParameter.from(UserHandle.class, USER_HANDLE), 164 ClassParameter.from(long.class, System.currentTimeMillis())); 165 shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo); 166 assertThat(launcherApps.getActivityList(TEST_PACKAGE_NAME, USER_HANDLE)) 167 .contains(launcherActivityInfo); 168 } 169 170 @Test 171 @Config(minSdk = N) testGetActivityList()172 public void testGetActivityList() { 173 assertThat(launcherApps.getActivityList(TEST_PACKAGE_NAME, USER_HANDLE)).isEmpty(); 174 175 LauncherActivityInfo launcherActivityInfo1 = 176 createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE); 177 178 shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo1); 179 assertThat(launcherApps.getActivityList(TEST_PACKAGE_NAME, USER_HANDLE)) 180 .contains(launcherActivityInfo1); 181 } 182 183 @Test 184 @Config(minSdk = N) testGetActivityList_packageNull_getsActivitiesFromAllPackagesForUser()185 public void testGetActivityList_packageNull_getsActivitiesFromAllPackagesForUser() { 186 LauncherActivityInfo launcherActivityInfo1 = 187 createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE); 188 LauncherActivityInfo launcherActivityInfo2 = 189 createLauncherActivityInfo(TEST_PACKAGE_NAME_2, USER_HANDLE); 190 LauncherActivityInfo launcherActivityInfo3 = 191 createLauncherActivityInfo(TEST_PACKAGE_NAME_3, UserHandle.of(10)); 192 shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo1); 193 shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo2); 194 shadowOf(launcherApps).addActivity(UserHandle.of(10), launcherActivityInfo3); 195 196 assertThat(launcherApps.getActivityList(null, USER_HANDLE)) 197 .containsExactly(launcherActivityInfo1, launcherActivityInfo2); 198 } 199 200 @Test 201 @Config(minSdk = L) testIsActivityEnabled()202 public void testIsActivityEnabled() { 203 ComponentName c1 = new ComponentName(ApplicationProvider.getApplicationContext(), "Activity1"); 204 ComponentName c2 = new ComponentName(ApplicationProvider.getApplicationContext(), "Activity2"); 205 ComponentName c3 = new ComponentName("other", "Activity1"); 206 assertThat(launcherApps.isActivityEnabled(c1, USER_HANDLE)).isFalse(); 207 208 shadowOf(launcherApps).setActivityEnabled(USER_HANDLE, c1); 209 assertThat(launcherApps.isActivityEnabled(c1, USER_HANDLE)).isTrue(); 210 assertThat(launcherApps.isActivityEnabled(c2, USER_HANDLE)).isFalse(); 211 assertThat(launcherApps.isActivityEnabled(c3, USER_HANDLE)).isFalse(); 212 } 213 214 @Test 215 @Config(minSdk = O) testGetApplicationInfo_packageNotFound()216 public void testGetApplicationInfo_packageNotFound() throws Exception { 217 Throwable throwable = 218 assertThrows( 219 NameNotFoundException.class, 220 () -> launcherApps.getApplicationInfo(TEST_PACKAGE_NAME, 0, USER_HANDLE)); 221 222 assertThat(throwable) 223 .hasMessageThat() 224 .isEqualTo( 225 "Package " + TEST_PACKAGE_NAME + " not found for user " + USER_HANDLE.getIdentifier()); 226 } 227 228 @Test testGetApplicationInfo_incorrectPackage()229 public void testGetApplicationInfo_incorrectPackage() throws Exception { 230 ApplicationInfo applicationInfo = new ApplicationInfo(); 231 applicationInfo.name = "Test app"; 232 shadowOf(launcherApps).addApplicationInfo(USER_HANDLE, TEST_PACKAGE_NAME_2, applicationInfo); 233 234 Throwable throwable = 235 assertThrows( 236 NameNotFoundException.class, 237 () -> launcherApps.getApplicationInfo(TEST_PACKAGE_NAME, 0, USER_HANDLE)); 238 239 assertThat(throwable) 240 .hasMessageThat() 241 .isEqualTo( 242 "Package " + TEST_PACKAGE_NAME + " not found for user " + USER_HANDLE.getIdentifier()); 243 } 244 245 @Test testGetApplicationInfo_findsApplicationInfo()246 public void testGetApplicationInfo_findsApplicationInfo() throws Exception { 247 ApplicationInfo applicationInfo = new ApplicationInfo(); 248 applicationInfo.name = "Test app"; 249 shadowOf(launcherApps).addApplicationInfo(USER_HANDLE, TEST_PACKAGE_NAME, applicationInfo); 250 251 assertThat(launcherApps.getApplicationInfo(TEST_PACKAGE_NAME, 0, USER_HANDLE)) 252 .isEqualTo(applicationInfo); 253 } 254 255 @Test testCallbackFiresWhenShortcutAddedOrRemoved()256 public void testCallbackFiresWhenShortcutAddedOrRemoved() throws Exception { 257 final Boolean[] wasCalled = new Boolean[] {false}; 258 final CountDownLatch latch1 = new CountDownLatch(1); 259 final CountDownLatch latch2 = new CountDownLatch(2); 260 261 final String packageName = ApplicationProvider.getApplicationContext().getPackageName(); 262 263 HandlerThread handlerThread = new HandlerThread("test"); 264 handlerThread.start(); 265 try { 266 LauncherApps.Callback callback = 267 new DefaultCallback() { 268 @Override 269 public void onShortcutsChanged( 270 String packageName, List<ShortcutInfo> shortcuts, UserHandle user) { 271 assertEquals(shortcuts.get(0).getPackage(), packageName); 272 wasCalled[0] = true; 273 latch1.countDown(); 274 latch2.countDown(); 275 } 276 }; 277 launcherApps.registerCallback(callback, new Handler(handlerThread.getLooper())); 278 shadowOf(launcherApps) 279 .addDynamicShortcut( 280 new ShortcutInfo.Builder(ApplicationProvider.getApplicationContext(), "ID").build()); 281 shadowLooper(handlerThread.getLooper()).idle(); 282 283 latch1.await(1, TimeUnit.SECONDS); 284 assertTrue(wasCalled[0]); 285 286 wasCalled[0] = false; 287 launcherApps.pinShortcuts(packageName, new ArrayList<>(), Process.myUserHandle()); 288 shadowLooper(handlerThread.getLooper()).idle(); 289 latch2.await(1, TimeUnit.SECONDS); 290 assertTrue(wasCalled[0]); 291 } finally { 292 handlerThread.quit(); 293 } 294 } 295 296 @Test testGetShortcuts()297 public void testGetShortcuts() { 298 final ShortcutInfo shortcut1 = 299 new ShortcutInfo.Builder(ApplicationProvider.getApplicationContext(), "ID1").build(); 300 final ShortcutInfo shortcut2 = 301 new ShortcutInfo.Builder(ApplicationProvider.getApplicationContext(), "ID2").build(); 302 303 shadowOf(launcherApps).addDynamicShortcut(shortcut1); 304 shadowOf(launcherApps).addDynamicShortcut(shortcut2); 305 306 assertThat(getPinnedShortcuts(null, null)).containsExactly(shortcut1, shortcut2); 307 } 308 309 @Test testGetShortcutsWithFilters()310 public void testGetShortcutsWithFilters() { 311 String myPackage = ApplicationProvider.getApplicationContext().getPackageName(); 312 String otherPackage = "other"; 313 ComponentName c1 = new ComponentName(ApplicationProvider.getApplicationContext(), "Activity1"); 314 ComponentName c2 = new ComponentName(ApplicationProvider.getApplicationContext(), "Activity2"); 315 ComponentName c3 = new ComponentName(otherPackage, "Activity1"); 316 317 final ShortcutInfo shortcut1 = 318 new ShortcutInfo.Builder(ApplicationProvider.getApplicationContext(), "ID1") 319 .setActivity(c1) 320 .build(); 321 final ShortcutInfo shortcut2 = 322 new ShortcutInfo.Builder(ApplicationProvider.getApplicationContext(), "ID2") 323 .setActivity(c2) 324 .build(); 325 final ShortcutInfo shortcut3 = 326 new ShortcutInfo.Builder(ApplicationProvider.getApplicationContext(), "ID3") 327 .setActivity(c3) 328 .build(); 329 reflector(ReflectorShortcutInfo.class, shortcut3).setPackage(otherPackage); 330 331 shadowOf(launcherApps).addDynamicShortcut(shortcut1); 332 shadowOf(launcherApps).addDynamicShortcut(shortcut2); 333 shadowOf(launcherApps).addDynamicShortcut(shortcut3); 334 335 assertThat(getPinnedShortcuts(otherPackage, null)).containsExactly(shortcut3); 336 assertThat(getPinnedShortcuts(myPackage, null)).containsExactly(shortcut1, shortcut2); 337 assertThat(getPinnedShortcuts(null, c1)).containsExactly(shortcut1); 338 assertThat(getPinnedShortcuts(null, c2)).containsExactly(shortcut2); 339 assertThat(getPinnedShortcuts(null, c3)).containsExactly(shortcut3); 340 } 341 342 @Test testHasShortcutHostPermission()343 public void testHasShortcutHostPermission() { 344 shadowOf(launcherApps).setHasShortcutHostPermission(true); 345 assertThat(launcherApps.hasShortcutHostPermission()).isTrue(); 346 } 347 348 @Test testHasShortcutHostPermission_returnsFalseByDefault()349 public void testHasShortcutHostPermission_returnsFalseByDefault() { 350 assertThat(launcherApps.hasShortcutHostPermission()).isFalse(); 351 } 352 353 @Test 354 @Config(minSdk = P) getSuspendedPackageLauncherExtras_returnsBundle()355 public void getSuspendedPackageLauncherExtras_returnsBundle() { 356 Bundle bundle = new Bundle(); 357 bundle.putInt("suspended_app", 5); 358 shadowOf(launcherApps) 359 .addSuspendedPackageLauncherExtras(USER_HANDLE, TEST_PACKAGE_NAME_2, bundle); 360 361 assertThat(launcherApps.getSuspendedPackageLauncherExtras(TEST_PACKAGE_NAME_2, USER_HANDLE)) 362 .isEqualTo(bundle); 363 } 364 365 @Test 366 @Config(minSdk = P) getSuspendedPackageLauncherExtras_returnsEmptyBundle()367 public void getSuspendedPackageLauncherExtras_returnsEmptyBundle() { 368 Throwable throwable = 369 assertThrows( 370 NameNotFoundException.class, 371 () -> launcherApps.getSuspendedPackageLauncherExtras(TEST_PACKAGE_NAME, USER_HANDLE)); 372 373 assertThat(throwable) 374 .hasMessageThat() 375 .isEqualTo( 376 "Suspended package extras for " 377 + TEST_PACKAGE_NAME 378 + " not found for user " 379 + USER_HANDLE.getIdentifier()); 380 } 381 getPinnedShortcuts(String packageName, ComponentName activity)382 private List<ShortcutInfo> getPinnedShortcuts(String packageName, ComponentName activity) { 383 ShortcutQuery query = new ShortcutQuery(); 384 query.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_PINNED); 385 query.setPackage(packageName); 386 query.setActivity(activity); 387 return launcherApps.getShortcuts(query, Process.myUserHandle()); 388 } 389 createLauncherActivityInfo( String packageName, UserHandle userHandle)390 private LauncherActivityInfo createLauncherActivityInfo( 391 String packageName, UserHandle userHandle) { 392 ActivityInfo info = new ActivityInfo(); 393 info.packageName = packageName; 394 info.name = packageName; 395 info.nonLocalizedLabel = packageName; 396 if (RuntimeEnvironment.getApiLevel() <= R) { 397 return ReflectionHelpers.callConstructor( 398 LauncherActivityInfo.class, 399 ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()), 400 ClassParameter.from(ActivityInfo.class, info), 401 ClassParameter.from(UserHandle.class, userHandle)); 402 } else if (RuntimeEnvironment.getApiLevel() <= TIRAMISU) { 403 LauncherActivityInfoInternal launcherActivityInfoInternal = 404 ReflectionHelpers.callConstructor( 405 LauncherActivityInfoInternal.class, 406 ClassParameter.from(ActivityInfo.class, info), 407 ClassParameter.from(IncrementalStatesInfo.class, null)); 408 409 return ReflectionHelpers.callConstructor( 410 LauncherActivityInfo.class, 411 ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()), 412 ClassParameter.from(UserHandle.class, userHandle), 413 ClassParameter.from(LauncherActivityInfoInternal.class, launcherActivityInfoInternal)); 414 } else { 415 416 LauncherActivityInfoInternal launcherActivityInfoInternal = 417 ReflectionHelpers.callConstructor( 418 LauncherActivityInfoInternal.class, 419 ClassParameter.from(ActivityInfo.class, info), 420 ClassParameter.from(IncrementalStatesInfo.class, null), 421 ClassParameter.from(UserHandle.class, userHandle)); 422 423 return ReflectionHelpers.callConstructor( 424 LauncherActivityInfo.class, 425 ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()), 426 ClassParameter.from(LauncherActivityInfoInternal.class, launcherActivityInfoInternal)); 427 } 428 } 429 } 430