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 17 package com.android.cts.splitapp; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static junit.framework.Assert.assertEquals; 22 import static junit.framework.Assert.assertFalse; 23 import static junit.framework.Assert.assertNull; 24 import static junit.framework.Assert.assertTrue; 25 import static junit.framework.Assert.fail; 26 27 import static org.junit.Assert.assertNotSame; 28 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 29 import static org.xmlpull.v1.XmlPullParser.START_TAG; 30 31 import android.app.Activity; 32 import android.content.BroadcastReceiver; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.ComponentInfo; 39 import android.content.pm.PackageInfo; 40 import android.content.pm.PackageManager; 41 import android.content.pm.ProviderInfo; 42 import android.content.pm.ResolveInfo; 43 import android.content.res.Configuration; 44 import android.content.res.Resources; 45 import android.database.Cursor; 46 import android.database.sqlite.SQLiteDatabase; 47 import android.graphics.Bitmap; 48 import android.graphics.Canvas; 49 import android.graphics.drawable.Drawable; 50 import android.net.Uri; 51 import android.os.Build; 52 import android.os.ConditionVariable; 53 import android.os.Environment; 54 import android.system.Os; 55 import android.system.StructStat; 56 import android.test.MoreAsserts; 57 import android.util.DisplayMetrics; 58 import android.util.Log; 59 60 import androidx.test.InstrumentationRegistry; 61 import androidx.test.rule.ActivityTestRule; 62 import androidx.test.runner.AndroidJUnit4; 63 64 import com.android.compatibility.common.util.SystemUtil; 65 import com.android.cts.splitapp.TestThemeHelper.ThemeColors; 66 67 import org.junit.After; 68 import org.junit.Before; 69 import org.junit.Rule; 70 import org.junit.Test; 71 import org.junit.runner.RunWith; 72 import org.xmlpull.v1.XmlPullParser; 73 import org.xmlpull.v1.XmlPullParserException; 74 75 import java.io.BufferedReader; 76 import java.io.DataInputStream; 77 import java.io.DataOutputStream; 78 import java.io.File; 79 import java.io.FileInputStream; 80 import java.io.FileOutputStream; 81 import java.io.IOException; 82 import java.io.InputStreamReader; 83 import java.lang.reflect.Field; 84 import java.lang.reflect.Method; 85 import java.util.Arrays; 86 import java.util.List; 87 import java.util.Locale; 88 import java.util.stream.Collectors; 89 90 @RunWith(AndroidJUnit4.class) 91 public class SplitAppTest { 92 private static final String TAG = "SplitAppTest"; 93 private static final String PKG = "com.android.cts.splitapp"; 94 private static final String NORESTART_PKG = "com.android.cts.norestart"; 95 96 private static final long MB_IN_BYTES = 1 * 1024 * 1024; 97 98 public static boolean sFeatureTouched = false; 99 public static String sFeatureValue = null; 100 101 private static final String BASE_THEME_ACTIVITY = ".ThemeActivity"; 102 private static final String WARM_THEME_ACTIVITY = ".WarmThemeActivity"; 103 private static final String ROSE_THEME_ACTIVITY = ".RoseThemeActivity"; 104 105 private static final ComponentName FEATURE_WARM_EMPTY_PROVIDER_NAME = 106 ComponentName.createRelative(PKG, ".feature.warm.EmptyProvider"); 107 private static final ComponentName FEATURE_WARM_EMPTY_SERVICE_NAME = 108 ComponentName.createRelative(PKG, ".feature.warm.EmptyService"); 109 110 private static final Uri INSTANT_APP_NORESTART_URI = Uri.parse( 111 "https://cts.android.com/norestart"); 112 113 @Rule 114 public ActivityTestRule<Activity> mActivityRule = 115 new ActivityTestRule<>(Activity.class, true /*initialTouchMode*/, 116 false /*launchActivity*/); 117 118 @Before setUp()119 public void setUp() { 120 setAppLinksUserSelection(NORESTART_PKG, INSTANT_APP_NORESTART_URI.getHost(), 121 true /*enabled*/); 122 } 123 124 @After tearDown()125 public void tearDown() { 126 setAppLinksUserSelection(NORESTART_PKG, INSTANT_APP_NORESTART_URI.getHost(), 127 false /*enabled*/); 128 } 129 130 @Test testNothing()131 public void testNothing() throws Exception { 132 } 133 134 @Test testSingleBase()135 public void testSingleBase() throws Exception { 136 final Resources r = getContext().getResources(); 137 final PackageManager pm = getContext().getPackageManager(); 138 139 // Should have untouched resources from base 140 assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled)); 141 142 assertEquals("blue", r.getString(R.string.my_string1)); 143 assertEquals("purple", r.getString(R.string.my_string2)); 144 145 assertEquals(0xff00ff00, r.getColor(R.color.my_color)); 146 assertEquals(123, r.getInteger(R.integer.my_integer)); 147 148 assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta))); 149 150 // We know about drawable IDs, but they're stripped from base 151 try { 152 r.getDrawable(R.drawable.image); 153 fail("Unexpected drawable in base"); 154 } catch (Resources.NotFoundException expected) { 155 } 156 157 // Should have base assets 158 assertAssetContents(r, "file1.txt", "FILE1"); 159 assertAssetContents(r, "dir/dirfile1.txt", "DIRFILE1"); 160 161 try { 162 assertAssetContents(r, "file2.txt", null); 163 fail("Unexpected asset file2"); 164 } catch (IOException expected) { 165 } 166 167 // Should only have base manifest items 168 Intent intent = new Intent(Intent.ACTION_MAIN); 169 intent.addCategory(Intent.CATEGORY_LAUNCHER); 170 intent.setPackage(PKG); 171 172 List<ResolveInfo> result = pm.queryIntentActivities(intent, 0); 173 assertEquals(1, result.size()); 174 assertEquals("com.android.cts.splitapp.MyActivity", result.get(0).activityInfo.name); 175 176 // Activity with split name `feature_warm` cannot be found. 177 intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST"); 178 intent.setPackage(PKG); 179 assertThat(pm.queryIntentActivities(intent, 0).stream().noneMatch( 180 info -> info.activityInfo.name.equals( 181 "com.android.cts.splitapp.feature.warm.EmptyActivity"))).isTrue(); 182 183 // Receiver disabled by default in base 184 intent = new Intent(Intent.ACTION_DATE_CHANGED); 185 intent.setPackage(PKG); 186 187 result = pm.queryBroadcastReceivers(intent, 0); 188 assertEquals(0, result.size()); 189 190 // We shouldn't have any native code in base 191 try { 192 Native.add(2, 4); 193 fail("Unexpected native code in base"); 194 } catch (UnsatisfiedLinkError expected) { 195 } 196 } 197 198 @Test testDensitySingle()199 public void testDensitySingle() throws Exception { 200 final Resources r = getContext().getResources(); 201 202 // We should still have base resources 203 assertEquals("blue", r.getString(R.string.my_string1)); 204 assertEquals("purple", r.getString(R.string.my_string2)); 205 206 // Now we know about drawables, but only mdpi 207 final Drawable d = r.getDrawable(R.drawable.image); 208 assertEquals(0xff7e00ff, getDrawableColor(d)); 209 } 210 211 @Test testDensityAll()212 public void testDensityAll() throws Exception { 213 final Resources r = getContext().getResources(); 214 215 // We should still have base resources 216 assertEquals("blue", r.getString(R.string.my_string1)); 217 assertEquals("purple", r.getString(R.string.my_string2)); 218 219 // Pretend that we're at each density 220 updateDpi(r, DisplayMetrics.DENSITY_MEDIUM); 221 assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image))); 222 223 updateDpi(r, DisplayMetrics.DENSITY_HIGH); 224 assertEquals(0xff00fcff, getDrawableColor(r.getDrawable(R.drawable.image))); 225 226 updateDpi(r, DisplayMetrics.DENSITY_XHIGH); 227 assertEquals(0xff80ff00, getDrawableColor(r.getDrawable(R.drawable.image))); 228 229 updateDpi(r, DisplayMetrics.DENSITY_XXHIGH); 230 assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image))); 231 } 232 233 @Test testDensityBest1()234 public void testDensityBest1() throws Exception { 235 final Resources r = getContext().getResources(); 236 237 // Pretend that we're really high density, but we only have mdpi installed 238 updateDpi(r, DisplayMetrics.DENSITY_XXHIGH); 239 assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image))); 240 } 241 242 @Test testDensityBest2()243 public void testDensityBest2() throws Exception { 244 final Resources r = getContext().getResources(); 245 246 // Pretend that we're really high density, and now we have better match 247 updateDpi(r, DisplayMetrics.DENSITY_XXHIGH); 248 assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image))); 249 } 250 251 @Test testApi()252 public void testApi() throws Exception { 253 final Resources r = getContext().getResources(); 254 final PackageManager pm = getContext().getPackageManager(); 255 256 // We should have updated boolean, different from base 257 assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled)); 258 259 // Receiver should be enabled now 260 Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); 261 intent.setPackage(PKG); 262 263 List<ResolveInfo> result = pm.queryBroadcastReceivers(intent, 0); 264 assertEquals(1, result.size()); 265 assertEquals("com.android.cts.splitapp.MyReceiver", result.get(0).activityInfo.name); 266 } 267 268 @Test testLocale()269 public void testLocale() throws Exception { 270 final Resources r = getContext().getResources(); 271 272 updateLocale(r, Locale.ENGLISH); 273 assertEquals("blue", r.getString(R.string.my_string1)); 274 assertEquals("purple", r.getString(R.string.my_string2)); 275 276 updateLocale(r, Locale.GERMAN); 277 assertEquals("blau", r.getString(R.string.my_string1)); 278 assertEquals("purple", r.getString(R.string.my_string2)); 279 280 updateLocale(r, Locale.FRENCH); 281 assertEquals("blue", r.getString(R.string.my_string1)); 282 assertEquals("pourpre", r.getString(R.string.my_string2)); 283 } 284 285 @Test testNative()286 public void testNative() throws Exception { 287 Log.d(TAG, "testNative() thinks it's using ABI " + Native.arch()); 288 289 // Make sure we can do the maths 290 assertEquals(11642, Native.add(4933, 6709)); 291 } 292 293 @Test testNativeRevision_sub_shouldImplementBadly()294 public void testNativeRevision_sub_shouldImplementBadly() throws Exception { 295 assertNotSame(1, Native.sub(0, -1)); 296 } 297 298 @Test testNativeRevision_sub_shouldImplementWell()299 public void testNativeRevision_sub_shouldImplementWell() throws Exception { 300 assertEquals(1, Native.sub(0, -1)); 301 } 302 303 @Test testNative64Bit()304 public void testNative64Bit() throws Exception { 305 Log.d(TAG, "The device supports 32Bit ABIs \"" 306 + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \"" 307 + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\""); 308 309 assertThat(Native.getAbiBitness()).isEqualTo(64); 310 } 311 312 @Test testNative32Bit()313 public void testNative32Bit() throws Exception { 314 Log.d(TAG, "The device supports 32Bit ABIs \"" 315 + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \"" 316 + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\""); 317 318 assertThat(Native.getAbiBitness()).isEqualTo(32); 319 } 320 321 @Test testNative_getNumberADirectly_shouldBeSeven()322 public void testNative_getNumberADirectly_shouldBeSeven() throws Exception { 323 assertThat(Native.getNumberADirectly()).isEqualTo(7); 324 } 325 326 @Test testNative_getNumberAViaProxy_shouldBeSeven()327 public void testNative_getNumberAViaProxy_shouldBeSeven() throws Exception { 328 assertThat(Native.getNumberAViaProxy()).isEqualTo(7); 329 } 330 331 @Test testNative_getNumberBDirectly_shouldBeEleven()332 public void testNative_getNumberBDirectly_shouldBeEleven() throws Exception { 333 assertThat(Native.getNumberBDirectly()).isEqualTo(11); 334 } 335 336 @Test testNative_getNumberBViaProxy_shouldBeEleven()337 public void testNative_getNumberBViaProxy_shouldBeEleven() throws Exception { 338 assertThat(Native.getNumberBViaProxy()).isEqualTo(11); 339 } 340 341 @Test testFeatureWarmBase()342 public void testFeatureWarmBase() throws Exception { 343 final Resources r = getContext().getResources(); 344 final PackageManager pm = getContext().getPackageManager(); 345 346 // Should have untouched resources from base 347 assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled)); 348 349 assertEquals("blue", r.getString(R.string.my_string1)); 350 assertEquals("purple", r.getString(R.string.my_string2)); 351 352 assertEquals(0xff00ff00, r.getColor(R.color.my_color)); 353 assertEquals(123, r.getInteger(R.integer.my_integer)); 354 355 assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta))); 356 357 // And that we can access resources from feature 358 assertEquals("red", r.getString(r.getIdentifier( 359 "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG))); 360 assertEquals(123, r.getInteger(r.getIdentifier( 361 "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG))); 362 363 final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR"); 364 final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null); 365 final int intId = (int) featR.getDeclaredField("feature_integer").get(null); 366 final int stringId = (int) featR.getDeclaredField("feature_string").get(null); 367 assertEquals(true, r.getBoolean(boolId)); 368 assertEquals(123, r.getInteger(intId)); 369 assertEquals("red", r.getString(stringId)); 370 371 // Should have both base and feature assets 372 assertAssetContents(r, "file1.txt", "FILE1"); 373 assertAssetContents(r, "file2.txt", "FILE2"); 374 assertAssetContents(r, "dir/dirfile1.txt", "DIRFILE1"); 375 assertAssetContents(r, "dir/dirfile2.txt", "DIRFILE2"); 376 377 // Should have both base and feature components 378 Intent intent = new Intent(Intent.ACTION_MAIN); 379 intent.addCategory(Intent.CATEGORY_LAUNCHER); 380 intent.setPackage(PKG); 381 List<ResolveInfo> result = pm.queryIntentActivities(intent, 0); 382 assertEquals(2, result.size()); 383 assertEquals("com.android.cts.splitapp.MyActivity", result.get(0).activityInfo.name); 384 assertEquals("com.android.cts.splitapp.FeatureActivity", result.get(1).activityInfo.name); 385 386 // Receiver only enabled in feature 387 intent = new Intent(Intent.ACTION_DATE_CHANGED); 388 intent.setPackage(PKG); 389 result = pm.queryBroadcastReceivers(intent, 0); 390 assertEquals(1, result.size()); 391 assertEquals("com.android.cts.splitapp.FeatureReceiver", result.get(0).activityInfo.name); 392 393 // And we should have a service 394 intent = new Intent("com.android.cts.splitapp.service"); 395 intent.setPackage(PKG); 396 result = pm.queryIntentServices(intent, 0); 397 assertEquals(1, result.size()); 398 assertEquals("com.android.cts.splitapp.FeatureService", result.get(0).serviceInfo.name); 399 400 // And a provider too 401 ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp.provider", 0); 402 assertEquals("com.android.cts.splitapp.FeatureProvider", info.name); 403 404 // And assert that we spun up the provider in this process 405 final Class<?> provider = Class.forName("com.android.cts.splitapp.FeatureProvider"); 406 final Field field = provider.getDeclaredField("sCreated"); 407 assertTrue("Expected provider to have been created", (boolean) field.get(null)); 408 assertTrue("Expected provider to have touched us", sFeatureTouched); 409 assertEquals(r.getString(R.string.my_string1), sFeatureValue); 410 411 // Finally ensure that we can execute some code from split 412 final Class<?> logic = Class.forName("com.android.cts.splitapp.FeatureLogic"); 413 final Method method = logic.getDeclaredMethod("mult", new Class[] { 414 Integer.TYPE, Integer.TYPE }); 415 assertEquals(72, (int) method.invoke(null, 12, 6)); 416 417 // Make sure we didn't get an extra flag from feature split 418 assertTrue("Someone parsed application flag!", 419 (getContext().getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) == 0); 420 421 // Make sure we have permission from base APK 422 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null); 423 424 try { 425 // But no new permissions from the feature APK 426 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, null); 427 fail("Whaaa, we somehow gained permission from feature?"); 428 } catch (SecurityException expected) { 429 } 430 431 // Assert that activity declared in the base can be found after feature_warm installed 432 intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST"); 433 intent.setPackage(PKG); 434 assertThat(pm.queryIntentActivities(intent, 0).stream().anyMatch( 435 resolveInfo -> resolveInfo.activityInfo.name.equals( 436 "com.android.cts.splitapp.feature.warm.EmptyActivity"))).isTrue(); 437 } 438 createLaunchIntent()439 private Intent createLaunchIntent() { 440 final boolean isInstant = Boolean.parseBoolean( 441 InstrumentationRegistry.getArguments().getString("is_instant", "false")); 442 if (isInstant) { 443 final Intent i = new Intent(Intent.ACTION_VIEW); 444 i.addCategory(Intent.CATEGORY_BROWSABLE); 445 i.setData(INSTANT_APP_NORESTART_URI); 446 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 447 return i; 448 } else { 449 final Intent i = new Intent("com.android.cts.norestart.START"); 450 i.addCategory(Intent.CATEGORY_DEFAULT); 451 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 452 return i; 453 } 454 } 455 456 @Test testBaseInstalled()457 public void testBaseInstalled() throws Exception { 458 final ConditionVariable cv = new ConditionVariable(); 459 final BroadcastReceiver r = new BroadcastReceiver() { 460 @Override 461 public void onReceive(Context context, Intent intent) { 462 assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1)); 463 assertEquals(0, intent.getIntExtra("NEW_INTENT_COUNT", -1)); 464 assertNull(intent.getStringExtra("RESOURCE_CONTENT")); 465 cv.open(); 466 } 467 }; 468 final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST"); 469 getContext().registerReceiver(r, filter, 470 Context.RECEIVER_EXPORTED | Context.RECEIVER_VISIBLE_TO_INSTANT_APPS); 471 final Intent i = createLaunchIntent(); 472 getContext().startActivity(i); 473 assertTrue(cv.block(2000L)); 474 getContext().unregisterReceiver(r); 475 } 476 477 /** 478 * Tests a running activity remains active while a new feature split is installed. 479 * <p> 480 * Prior to running this test, the activity must be started. That is currently 481 * done in {@link #testBaseInstalled()}. 482 */ 483 @Test testFeatureInstalled()484 public void testFeatureInstalled() throws Exception { 485 final ConditionVariable cv = new ConditionVariable(); 486 final BroadcastReceiver r = new BroadcastReceiver() { 487 @Override 488 public void onReceive(Context context, Intent intent) { 489 assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1)); 490 assertEquals(1, intent.getIntExtra("NEW_INTENT_COUNT", -1)); 491 assertEquals("Hello feature!", intent.getStringExtra("RESOURCE_CONTENT")); 492 cv.open(); 493 } 494 }; 495 final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST"); 496 getContext().registerReceiver(r, filter, 497 Context.RECEIVER_EXPORTED | Context.RECEIVER_VISIBLE_TO_INSTANT_APPS); 498 final Intent i = createLaunchIntent(); 499 getContext().startActivity(i); 500 assertTrue(cv.block(2000L)); 501 getContext().unregisterReceiver(r); 502 } 503 504 @Test testFeatureWarmApi()505 public void testFeatureWarmApi() throws Exception { 506 final Resources r = getContext().getResources(); 507 final PackageManager pm = getContext().getPackageManager(); 508 509 // Should have untouched resources from base 510 assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled)); 511 512 // And that we can access resources from feature 513 assertEquals(321, r.getInteger(r.getIdentifier( 514 "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG))); 515 516 final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR"); 517 final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null); 518 final int intId = (int) featR.getDeclaredField("feature_integer").get(null); 519 final int stringId = (int) featR.getDeclaredField("feature_string").get(null); 520 assertEquals(false, r.getBoolean(boolId)); 521 assertEquals(321, r.getInteger(intId)); 522 assertEquals("red", r.getString(stringId)); 523 524 // And now both receivers should be disabled 525 Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); 526 intent.setPackage(PKG); 527 List<ResolveInfo> result = pm.queryBroadcastReceivers(intent, 0); 528 assertEquals(0, result.size()); 529 } 530 531 @Test testInheritUpdatedBase_withRevisionA()532 public void testInheritUpdatedBase_withRevisionA() throws Exception { 533 final Resources r = getContext().getResources(); 534 final PackageManager pm = getContext().getPackageManager(); 535 536 // Resources should have been updated 537 assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled)); 538 539 assertEquals("blue-revision", r.getString(R.string.my_string1)); 540 assertEquals("purple-revision", r.getString(R.string.my_string2)); 541 542 assertEquals(0xff00ffff, r.getColor(R.color.my_color)); 543 assertEquals(456, r.getInteger(R.integer.my_integer)); 544 545 // Also, new resources could be found 546 assertEquals("new string", r.getString(r.getIdentifier( 547 "my_new_string", "string", PKG))); 548 549 assertAssetContents(r, "fileA.txt", "FILEA"); 550 assertAssetContents(r, "dir/dirfileA.txt", "DIRFILEA"); 551 552 // Activity of ACTION_MAIN should have been updated to .revision_a.MyActivity 553 Intent intent = new Intent(Intent.ACTION_MAIN); 554 intent.addCategory(Intent.CATEGORY_LAUNCHER); 555 intent.setPackage(PKG); 556 final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream() 557 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 558 assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.MyActivity"); 559 560 // Receiver of DATE_CHANGED should have been updated to .revision_a.MyReceiver 561 intent = new Intent(Intent.ACTION_DATE_CHANGED); 562 intent.setPackage(PKG); 563 final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream() 564 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 565 assertThat(receiverNames).contains("com.android.cts.splitapp.revision_a.MyReceiver"); 566 567 // Provider should have been updated to .revision_a.MyProvider 568 final ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp", 0); 569 assertEquals("com.android.cts.splitapp.revision_a.MyProvider", info.name); 570 571 // And assert that we spun up the provider in this process 572 final Class<?> provider = Class.forName("com.android.cts.splitapp.revision_a.MyProvider"); 573 final Field field = provider.getDeclaredField("sCreated"); 574 assertTrue("Expected provider to have been created", (boolean) field.get(null)); 575 576 // Camera permission has been removed 577 try { 578 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null); 579 fail("Camera permission should not be granted"); 580 } catch (SecurityException expected) { 581 } 582 583 // New Vibrate permission should be granted 584 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, null); 585 } 586 587 @Test testInheritUpdatedSplit_withRevisionA()588 public void testInheritUpdatedSplit_withRevisionA() throws Exception { 589 final Resources r = getContext().getResources(); 590 final PackageManager pm = getContext().getPackageManager(); 591 592 // Resources should have been updated 593 assertEquals("red-revision", r.getString(r.getIdentifier( 594 "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG))); 595 assertEquals(456, r.getInteger(r.getIdentifier( 596 "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG))); 597 598 // Also, new resources could be found 599 assertEquals("feature new string", r.getString(r.getIdentifier( 600 "com.android.cts.splitapp.feature_warm:feature_new_string", "string", PKG))); 601 602 assertAssetContents(r, "fileFA.txt", "FILE_FA"); 603 assertAssetContents(r, "dir/dirfileFA.txt", "DIRFILE_FA"); 604 605 // Activity of ACTION_MAIN should have been updated to .revision_a.FeatureActivity 606 Intent intent = new Intent(Intent.ACTION_MAIN); 607 intent.addCategory(Intent.CATEGORY_LAUNCHER); 608 intent.setPackage(PKG); 609 final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream() 610 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 611 assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.FeatureActivity"); 612 613 // Receiver of DATE_CHANGED could not be found 614 intent = new Intent(Intent.ACTION_DATE_CHANGED); 615 intent.setPackage(PKG); 616 final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream() 617 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 618 assertThat(receiverNames).doesNotContain("com.android.cts.splitapp.FeatureReceiver"); 619 620 // Service of splitapp should have been updated to .revision_a.FeatureService 621 intent = new Intent("com.android.cts.splitapp.service"); 622 intent.setPackage(PKG); 623 final List<String> serviceNames = pm.queryIntentServices(intent, 0).stream() 624 .map(info -> info.serviceInfo.name).collect(Collectors.toList()); 625 assertThat(serviceNames).contains("com.android.cts.splitapp.revision_a.FeatureService"); 626 627 // Provider should have been updated to .revision_a.FeatureProvider 628 final ProviderInfo info = pm.resolveContentProvider( 629 "com.android.cts.splitapp.provider", 0); 630 assertEquals("com.android.cts.splitapp.revision_a.FeatureProvider", info.name); 631 632 // And assert that we spun up the provider in this process 633 final Class<?> provider = Class.forName( 634 "com.android.cts.splitapp.revision_a.FeatureProvider"); 635 final Field field = provider.getDeclaredField("sCreated"); 636 assertTrue("Expected provider to have been created", (boolean) field.get(null)); 637 } 638 639 /** 640 * Write app data in a number of locations that expect to remain intact over 641 * long periods of time, such as across app moves. 642 */ 643 @Test testDataWrite()644 public void testDataWrite() throws Exception { 645 final String token = String.valueOf(android.os.Process.myUid()); 646 writeString(getContext().getFileStreamPath("my_int"), token); 647 648 final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db", 649 Context.MODE_PRIVATE, null); 650 try { 651 db.execSQL("DROP TABLE IF EXISTS my_table"); 652 db.execSQL("CREATE TABLE my_table(value INTEGER)"); 653 db.execSQL("INSERT INTO my_table VALUES (101), (102), (103)"); 654 } finally { 655 db.close(); 656 } 657 } 658 659 /** 660 * Verify that data written by {@link #testDataWrite()} is still intact. 661 */ 662 @Test testDataRead()663 public void testDataRead() throws Exception { 664 final String token = String.valueOf(android.os.Process.myUid()); 665 assertEquals(token, readString(getContext().getFileStreamPath("my_int"))); 666 667 final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db", 668 Context.MODE_PRIVATE, null); 669 try { 670 final Cursor cursor = db.query("my_table", null, null, null, null, null, "value ASC"); 671 try { 672 assertEquals(3, cursor.getCount()); 673 assertTrue(cursor.moveToPosition(0)); 674 assertEquals(101, cursor.getInt(0)); 675 assertTrue(cursor.moveToPosition(1)); 676 assertEquals(102, cursor.getInt(0)); 677 assertTrue(cursor.moveToPosition(2)); 678 assertEquals(103, cursor.getInt(0)); 679 } finally { 680 cursor.close(); 681 } 682 } finally { 683 db.close(); 684 } 685 } 686 687 /** 688 * Verify that app is installed on internal storage. 689 */ 690 @Test testDataInternal()691 public void testDataInternal() throws Exception { 692 final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath()); 693 final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath()); 694 assertEquals(internal.st_dev, actual.st_dev); 695 } 696 697 /** 698 * Verify that app is not installed on internal storage. 699 */ 700 @Test testDataNotInternal()701 public void testDataNotInternal() throws Exception { 702 final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath()); 703 final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath()); 704 MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev); 705 } 706 707 @Test testPrimaryDataWrite()708 public void testPrimaryDataWrite() throws Exception { 709 final String token = String.valueOf(android.os.Process.myUid()); 710 writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token); 711 } 712 713 @Test testPrimaryDataRead()714 public void testPrimaryDataRead() throws Exception { 715 final String token = String.valueOf(android.os.Process.myUid()); 716 assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext"))); 717 } 718 719 /** 720 * Verify shared storage behavior when on internal storage. 721 */ 722 @Test testPrimaryInternal()723 public void testPrimaryInternal() throws Exception { 724 assertTrue("emulated", Environment.isExternalStorageEmulated()); 725 assertFalse("removable", Environment.isExternalStorageRemovable()); 726 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 727 } 728 729 /** 730 * Verify shared storage behavior when on physical storage. 731 */ 732 @Test testPrimaryPhysical()733 public void testPrimaryPhysical() throws Exception { 734 assertFalse("emulated", Environment.isExternalStorageEmulated()); 735 assertTrue("removable", Environment.isExternalStorageRemovable()); 736 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 737 } 738 739 /** 740 * Verify shared storage behavior when on adopted storage. 741 */ 742 @Test testPrimaryAdopted()743 public void testPrimaryAdopted() throws Exception { 744 assertTrue("emulated", Environment.isExternalStorageEmulated()); 745 assertTrue("removable", Environment.isExternalStorageRemovable()); 746 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 747 } 748 749 /** 750 * Verify that shared storage is unmounted. 751 */ 752 @Test testPrimaryUnmounted()753 public void testPrimaryUnmounted() throws Exception { 754 MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED, 755 Environment.getExternalStorageState()); 756 } 757 758 /** 759 * Verify that shared storage lives on same volume as app. 760 */ 761 @Test testPrimaryOnSameVolume()762 public void testPrimaryOnSameVolume() throws Exception { 763 final File current = getContext().getFilesDir(); 764 final File primary = Environment.getExternalStorageDirectory(); 765 766 // Shared storage may jump through another filesystem for permission 767 // enforcement, so we verify that total/free space are identical. 768 final long totalDelta = Math.abs(current.getTotalSpace() - primary.getTotalSpace()); 769 final long freeDelta = Math.abs(current.getFreeSpace() - primary.getFreeSpace()); 770 if (totalDelta > MB_IN_BYTES * 300 || freeDelta > MB_IN_BYTES * 300) { 771 fail("Expected primary storage to be on same volume as app"); 772 } 773 } 774 775 @Test testCodeCacheWrite()776 public void testCodeCacheWrite() throws Exception { 777 assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile()); 778 assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile()); 779 } 780 781 @Test testCodeCacheRead()782 public void testCodeCacheRead() throws Exception { 783 assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists()); 784 assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists()); 785 } 786 787 @Test testRevision0_0()788 public void testRevision0_0() throws Exception { 789 final PackageInfo info = getContext().getPackageManager() 790 .getPackageInfo(getContext().getPackageName(), 0); 791 assertEquals(0, info.baseRevisionCode); 792 assertEquals(1, info.splitRevisionCodes.length); 793 assertEquals(0, info.splitRevisionCodes[0]); 794 } 795 796 @Test testRevision12_0()797 public void testRevision12_0() throws Exception { 798 final PackageInfo info = getContext().getPackageManager() 799 .getPackageInfo(getContext().getPackageName(), 0); 800 assertEquals(12, info.baseRevisionCode); 801 assertEquals(1, info.splitRevisionCodes.length); 802 assertEquals(0, info.splitRevisionCodes[0]); 803 } 804 805 @Test testRevision0_12()806 public void testRevision0_12() throws Exception { 807 final PackageInfo info = getContext().getPackageManager() 808 .getPackageInfo(getContext().getPackageName(), 0); 809 assertEquals(0, info.baseRevisionCode); 810 assertEquals(1, info.splitRevisionCodes.length); 811 assertEquals(12, info.splitRevisionCodes[0]); 812 } 813 814 @Test testComponentWithSplitName_singleBase()815 public void testComponentWithSplitName_singleBase() { 816 final PackageManager pm = getContext().getPackageManager(); 817 final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST"); 818 intent.setPackage(PKG); 819 820 // Service with split name `feature_warm` cannot be found 821 List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0); 822 assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo) 823 .equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue(); 824 825 // Provider with split name `feature_warm` cannot be found 826 resolveInfoList = pm.queryIntentContentProviders(intent, 0); 827 assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo) 828 .equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue(); 829 } 830 831 @Test testComponentWithSplitName_featureWarmInstalled()832 public void testComponentWithSplitName_featureWarmInstalled() throws Exception { 833 final PackageManager pm = getContext().getPackageManager(); 834 final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST"); 835 intent.setPackage(PKG); 836 837 // Service with split name `feature_warm` could be found 838 List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0); 839 assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo) 840 .equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue(); 841 842 // Provider with split name `feature_warm` could be found 843 resolveInfoList = pm.queryIntentContentProviders(intent, 0); 844 assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo) 845 .equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue(); 846 847 // And assert that we spun up the provider in this process 848 final Class<?> provider = Class.forName(FEATURE_WARM_EMPTY_PROVIDER_NAME.getClassName()); 849 final Field field = provider.getDeclaredField("sCreated"); 850 assertThat((boolean) field.get(null)).isTrue(); 851 } 852 853 @Test launchBaseActivity_withThemeBase_baseApplied()854 public void launchBaseActivity_withThemeBase_baseApplied() { 855 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base, 856 ThemeColors.BASE); 857 } 858 859 @Test launchBaseActivity_withThemeBaseLt_baseLtApplied()860 public void launchBaseActivity_withThemeBaseLt_baseLtApplied() { 861 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base, 862 ThemeColors.BASE_LT); 863 } 864 865 @Test launchBaseActivity_withThemeWarm_warmApplied()866 public void launchBaseActivity_withThemeWarm_warmApplied() { 867 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, 868 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM); 869 } 870 871 @Test launchBaseActivity_withThemeWarmLt_warmLtApplied()872 public void launchBaseActivity_withThemeWarmLt_warmLtApplied() { 873 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, 874 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT); 875 } 876 877 @Test launchWarmActivity_withThemeBase_baseApplied()878 public void launchWarmActivity_withThemeBase_baseApplied() { 879 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base, 880 ThemeColors.BASE); 881 } 882 883 @Test launchWarmActivity_withThemeBaseLt_baseLtApplied()884 public void launchWarmActivity_withThemeBaseLt_baseLtApplied() { 885 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base, 886 ThemeColors.BASE_LT); 887 } 888 889 @Test launchWarmActivity_withThemeWarm_warmApplied()890 public void launchWarmActivity_withThemeWarm_warmApplied() { 891 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 892 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM); 893 } 894 895 @Test launchWarmActivity_withThemeWarmLt_warmLtApplied()896 public void launchWarmActivity_withThemeWarmLt_warmLtApplied() { 897 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 898 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT); 899 } 900 901 @Test launchWarmActivity_withThemeRose_roseApplied()902 public void launchWarmActivity_withThemeRose_roseApplied() { 903 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 904 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE); 905 } 906 907 @Test launchWarmActivity_withThemeRoseLt_roseLtApplied()908 public void launchWarmActivity_withThemeRoseLt_roseLtApplied() { 909 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 910 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT); 911 } 912 913 @Test launchRoseActivity_withThemeWarm_warmApplied()914 public void launchRoseActivity_withThemeWarm_warmApplied() { 915 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 916 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM); 917 } 918 919 @Test launchRoseActivity_withThemeWarmLt_warmLtApplied()920 public void launchRoseActivity_withThemeWarmLt_warmLtApplied() { 921 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 922 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT); 923 } 924 925 @Test launchRoseActivity_withThemeRose_roseApplied()926 public void launchRoseActivity_withThemeRose_roseApplied() { 927 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 928 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE); 929 } 930 931 @Test launchRoseActivity_withThemeRoseLt_roseLtApplied()932 public void launchRoseActivity_withThemeRoseLt_roseLtApplied() { 933 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 934 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT); 935 } 936 assertActivityLaunchedAndThemeApplied(String activityName, int themeResId, ThemeColors themeColors)937 private void assertActivityLaunchedAndThemeApplied(String activityName, int themeResId, 938 ThemeColors themeColors) { 939 final Activity activity = mActivityRule.launchActivity( 940 getTestThemeIntent(activityName, themeResId)); 941 final TestThemeHelper expected = new TestThemeHelper(activity, themeResId); 942 expected.assertThemeValues(themeColors); 943 expected.assertThemeApplied(activity); 944 } 945 getContext()946 private static Context getContext() { 947 return InstrumentationRegistry.getInstrumentation().getTargetContext(); 948 } 949 updateDpi(Resources r, int densityDpi)950 private static void updateDpi(Resources r, int densityDpi) { 951 final Configuration c = new Configuration(r.getConfiguration()); 952 c.densityDpi = densityDpi; 953 r.updateConfiguration(c, r.getDisplayMetrics()); 954 } 955 updateLocale(Resources r, Locale locale)956 private static void updateLocale(Resources r, Locale locale) { 957 final Configuration c = new Configuration(r.getConfiguration()); 958 c.locale = locale; 959 r.updateConfiguration(c, r.getDisplayMetrics()); 960 } 961 getDrawableColor(Drawable d)962 private static int getDrawableColor(Drawable d) { 963 final Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), 964 Bitmap.Config.ARGB_8888); 965 final Canvas canvas = new Canvas(bitmap); 966 d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 967 d.draw(canvas); 968 return bitmap.getPixel(0, 0); 969 } 970 getXmlTestValue(XmlPullParser in)971 private static String getXmlTestValue(XmlPullParser in) throws XmlPullParserException, 972 IOException { 973 int type; 974 while ((type = in.next()) != END_DOCUMENT) { 975 if (type == START_TAG) { 976 final String tag = in.getName(); 977 if ("tag".equals(tag)) { 978 return in.getAttributeValue(null, "value"); 979 } 980 } 981 } 982 return null; 983 } 984 assertAssetContents(Resources r, String path, String expected)985 private static void assertAssetContents(Resources r, String path, String expected) 986 throws IOException { 987 BufferedReader in = null; 988 try { 989 in = new BufferedReader(new InputStreamReader(r.getAssets().open(path))); 990 assertEquals(expected, in.readLine()); 991 } finally { 992 if (in != null) in.close(); 993 } 994 } 995 writeString(File file, String value)996 private static void writeString(File file, String value) throws IOException { 997 final DataOutputStream os = new DataOutputStream(new FileOutputStream(file)); 998 try { 999 os.writeUTF(value); 1000 } finally { 1001 os.close(); 1002 } 1003 } 1004 readString(File file)1005 private static String readString(File file) throws IOException { 1006 final DataInputStream is = new DataInputStream(new FileInputStream(file)); 1007 try { 1008 return is.readUTF(); 1009 } finally { 1010 is.close(); 1011 } 1012 } 1013 resolveResourceId(String nameOfIdentifier)1014 private int resolveResourceId(String nameOfIdentifier) { 1015 final int resId = getContext().getResources().getIdentifier(nameOfIdentifier, null, null); 1016 assertTrue("Resource not found: " + nameOfIdentifier, resId != 0); 1017 return resId; 1018 } 1019 getTestThemeIntent(String activityName, int themeResId)1020 private static Intent getTestThemeIntent(String activityName, int themeResId) { 1021 final Intent intent = new Intent(ThemeActivity.INTENT_THEME_TEST); 1022 intent.setComponent(ComponentName.createRelative(PKG, activityName)); 1023 intent.putExtra(ThemeActivity.EXTRAS_THEME_RES_ID, themeResId); 1024 return intent; 1025 } 1026 getComponentName(ResolveInfo resolveInfo)1027 private static ComponentName getComponentName(ResolveInfo resolveInfo) { 1028 final ComponentInfo componentInfo = resolveInfo.activityInfo != null 1029 ? resolveInfo.activityInfo : resolveInfo.serviceInfo != null 1030 ? resolveInfo.serviceInfo : resolveInfo.providerInfo; 1031 if (componentInfo == null) { 1032 throw new AssertionError("Missing ComponentInfo in the ResolveInfo!"); 1033 } 1034 return new ComponentName(componentInfo.packageName, componentInfo.name); 1035 } 1036 setAppLinksUserSelection(String packageName, String uriHostName, boolean enabled)1037 private static void setAppLinksUserSelection(String packageName, String uriHostName, 1038 boolean enabled) { 1039 final String cmd = String.format("pm set-app-links-user-selection --user cur --package " 1040 + "%s %b %s", packageName, enabled, uriHostName); 1041 SystemUtil.runShellCommand(cmd); 1042 } 1043 } 1044