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, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS); 470 final Intent i = createLaunchIntent(); 471 getContext().startActivity(i); 472 assertTrue(cv.block(2000L)); 473 getContext().unregisterReceiver(r); 474 } 475 476 /** 477 * Tests a running activity remains active while a new feature split is installed. 478 * <p> 479 * Prior to running this test, the activity must be started. That is currently 480 * done in {@link #testBaseInstalled()}. 481 */ 482 @Test testFeatureInstalled()483 public void testFeatureInstalled() throws Exception { 484 final ConditionVariable cv = new ConditionVariable(); 485 final BroadcastReceiver r = new BroadcastReceiver() { 486 @Override 487 public void onReceive(Context context, Intent intent) { 488 assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1)); 489 assertEquals(1, intent.getIntExtra("NEW_INTENT_COUNT", -1)); 490 assertEquals("Hello feature!", intent.getStringExtra("RESOURCE_CONTENT")); 491 cv.open(); 492 } 493 }; 494 final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST"); 495 getContext().registerReceiver(r, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS); 496 final Intent i = createLaunchIntent(); 497 getContext().startActivity(i); 498 assertTrue(cv.block(2000L)); 499 getContext().unregisterReceiver(r); 500 } 501 502 @Test testFeatureWarmApi()503 public void testFeatureWarmApi() throws Exception { 504 final Resources r = getContext().getResources(); 505 final PackageManager pm = getContext().getPackageManager(); 506 507 // Should have untouched resources from base 508 assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled)); 509 510 // And that we can access resources from feature 511 assertEquals(321, r.getInteger(r.getIdentifier( 512 "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG))); 513 514 final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR"); 515 final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null); 516 final int intId = (int) featR.getDeclaredField("feature_integer").get(null); 517 final int stringId = (int) featR.getDeclaredField("feature_string").get(null); 518 assertEquals(false, r.getBoolean(boolId)); 519 assertEquals(321, r.getInteger(intId)); 520 assertEquals("red", r.getString(stringId)); 521 522 // And now both receivers should be disabled 523 Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); 524 intent.setPackage(PKG); 525 List<ResolveInfo> result = pm.queryBroadcastReceivers(intent, 0); 526 assertEquals(0, result.size()); 527 } 528 529 @Test testInheritUpdatedBase_withRevisionA()530 public void testInheritUpdatedBase_withRevisionA() throws Exception { 531 final Resources r = getContext().getResources(); 532 final PackageManager pm = getContext().getPackageManager(); 533 534 // Resources should have been updated 535 assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled)); 536 537 assertEquals("blue-revision", r.getString(R.string.my_string1)); 538 assertEquals("purple-revision", r.getString(R.string.my_string2)); 539 540 assertEquals(0xff00ffff, r.getColor(R.color.my_color)); 541 assertEquals(456, r.getInteger(R.integer.my_integer)); 542 543 // Also, new resources could be found 544 assertEquals("new string", r.getString(r.getIdentifier( 545 "my_new_string", "string", PKG))); 546 547 assertAssetContents(r, "fileA.txt", "FILEA"); 548 assertAssetContents(r, "dir/dirfileA.txt", "DIRFILEA"); 549 550 // Activity of ACTION_MAIN should have been updated to .revision_a.MyActivity 551 Intent intent = new Intent(Intent.ACTION_MAIN); 552 intent.addCategory(Intent.CATEGORY_LAUNCHER); 553 intent.setPackage(PKG); 554 final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream() 555 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 556 assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.MyActivity"); 557 558 // Receiver of DATE_CHANGED should have been updated to .revision_a.MyReceiver 559 intent = new Intent(Intent.ACTION_DATE_CHANGED); 560 intent.setPackage(PKG); 561 final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream() 562 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 563 assertThat(receiverNames).contains("com.android.cts.splitapp.revision_a.MyReceiver"); 564 565 // Provider should have been updated to .revision_a.MyProvider 566 final ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp", 0); 567 assertEquals("com.android.cts.splitapp.revision_a.MyProvider", info.name); 568 569 // And assert that we spun up the provider in this process 570 final Class<?> provider = Class.forName("com.android.cts.splitapp.revision_a.MyProvider"); 571 final Field field = provider.getDeclaredField("sCreated"); 572 assertTrue("Expected provider to have been created", (boolean) field.get(null)); 573 574 // Camera permission has been removed 575 try { 576 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null); 577 fail("Camera permission should not be granted"); 578 } catch (SecurityException expected) { 579 } 580 581 // New Vibrate permision should be granted 582 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, null); 583 } 584 585 @Test testInheritUpdatedSplit_withRevisionA()586 public void testInheritUpdatedSplit_withRevisionA() throws Exception { 587 final Resources r = getContext().getResources(); 588 final PackageManager pm = getContext().getPackageManager(); 589 590 // Resources should have been updated 591 assertEquals("red-revision", r.getString(r.getIdentifier( 592 "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG))); 593 assertEquals(456, r.getInteger(r.getIdentifier( 594 "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG))); 595 596 // Also, new resources could be found 597 assertEquals("feature new string", r.getString(r.getIdentifier( 598 "com.android.cts.splitapp.feature_warm:feature_new_string", "string", PKG))); 599 600 assertAssetContents(r, "fileFA.txt", "FILE_FA"); 601 assertAssetContents(r, "dir/dirfileFA.txt", "DIRFILE_FA"); 602 603 // Activity of ACTION_MAIN should have been updated to .revision_a.FeatureActivity 604 Intent intent = new Intent(Intent.ACTION_MAIN); 605 intent.addCategory(Intent.CATEGORY_LAUNCHER); 606 intent.setPackage(PKG); 607 final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream() 608 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 609 assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.FeatureActivity"); 610 611 // Receiver of DATE_CHANGED could not be found 612 intent = new Intent(Intent.ACTION_DATE_CHANGED); 613 intent.setPackage(PKG); 614 final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream() 615 .map(info -> info.activityInfo.name).collect(Collectors.toList()); 616 assertThat(receiverNames).doesNotContain("com.android.cts.splitapp.FeatureReceiver"); 617 618 // Service of splitapp should have been updated to .revision_a.FeatureService 619 intent = new Intent("com.android.cts.splitapp.service"); 620 intent.setPackage(PKG); 621 final List<String> serviceNames = pm.queryIntentServices(intent, 0).stream() 622 .map(info -> info.serviceInfo.name).collect(Collectors.toList()); 623 assertThat(serviceNames).contains("com.android.cts.splitapp.revision_a.FeatureService"); 624 625 // Provider should have been updated to .revision_a.FeatureProvider 626 final ProviderInfo info = pm.resolveContentProvider( 627 "com.android.cts.splitapp.provider", 0); 628 assertEquals("com.android.cts.splitapp.revision_a.FeatureProvider", info.name); 629 630 // And assert that we spun up the provider in this process 631 final Class<?> provider = Class.forName( 632 "com.android.cts.splitapp.revision_a.FeatureProvider"); 633 final Field field = provider.getDeclaredField("sCreated"); 634 assertTrue("Expected provider to have been created", (boolean) field.get(null)); 635 } 636 637 /** 638 * Write app data in a number of locations that expect to remain intact over 639 * long periods of time, such as across app moves. 640 */ 641 @Test testDataWrite()642 public void testDataWrite() throws Exception { 643 final String token = String.valueOf(android.os.Process.myUid()); 644 writeString(getContext().getFileStreamPath("my_int"), token); 645 646 final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db", 647 Context.MODE_PRIVATE, null); 648 try { 649 db.execSQL("DROP TABLE IF EXISTS my_table"); 650 db.execSQL("CREATE TABLE my_table(value INTEGER)"); 651 db.execSQL("INSERT INTO my_table VALUES (101), (102), (103)"); 652 } finally { 653 db.close(); 654 } 655 } 656 657 /** 658 * Verify that data written by {@link #testDataWrite()} is still intact. 659 */ 660 @Test testDataRead()661 public void testDataRead() throws Exception { 662 final String token = String.valueOf(android.os.Process.myUid()); 663 assertEquals(token, readString(getContext().getFileStreamPath("my_int"))); 664 665 final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db", 666 Context.MODE_PRIVATE, null); 667 try { 668 final Cursor cursor = db.query("my_table", null, null, null, null, null, "value ASC"); 669 try { 670 assertEquals(3, cursor.getCount()); 671 assertTrue(cursor.moveToPosition(0)); 672 assertEquals(101, cursor.getInt(0)); 673 assertTrue(cursor.moveToPosition(1)); 674 assertEquals(102, cursor.getInt(0)); 675 assertTrue(cursor.moveToPosition(2)); 676 assertEquals(103, cursor.getInt(0)); 677 } finally { 678 cursor.close(); 679 } 680 } finally { 681 db.close(); 682 } 683 } 684 685 /** 686 * Verify that app is installed on internal storage. 687 */ 688 @Test testDataInternal()689 public void testDataInternal() throws Exception { 690 final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath()); 691 final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath()); 692 assertEquals(internal.st_dev, actual.st_dev); 693 } 694 695 /** 696 * Verify that app is not installed on internal storage. 697 */ 698 @Test testDataNotInternal()699 public void testDataNotInternal() throws Exception { 700 final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath()); 701 final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath()); 702 MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev); 703 } 704 705 @Test testPrimaryDataWrite()706 public void testPrimaryDataWrite() throws Exception { 707 final String token = String.valueOf(android.os.Process.myUid()); 708 writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token); 709 } 710 711 @Test testPrimaryDataRead()712 public void testPrimaryDataRead() throws Exception { 713 final String token = String.valueOf(android.os.Process.myUid()); 714 assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext"))); 715 } 716 717 /** 718 * Verify shared storage behavior when on internal storage. 719 */ 720 @Test testPrimaryInternal()721 public void testPrimaryInternal() throws Exception { 722 assertTrue("emulated", Environment.isExternalStorageEmulated()); 723 assertFalse("removable", Environment.isExternalStorageRemovable()); 724 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 725 } 726 727 /** 728 * Verify shared storage behavior when on physical storage. 729 */ 730 @Test testPrimaryPhysical()731 public void testPrimaryPhysical() throws Exception { 732 assertFalse("emulated", Environment.isExternalStorageEmulated()); 733 assertTrue("removable", Environment.isExternalStorageRemovable()); 734 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 735 } 736 737 /** 738 * Verify shared storage behavior when on adopted storage. 739 */ 740 @Test testPrimaryAdopted()741 public void testPrimaryAdopted() throws Exception { 742 assertTrue("emulated", Environment.isExternalStorageEmulated()); 743 assertTrue("removable", Environment.isExternalStorageRemovable()); 744 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 745 } 746 747 /** 748 * Verify that shared storage is unmounted. 749 */ 750 @Test testPrimaryUnmounted()751 public void testPrimaryUnmounted() throws Exception { 752 MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED, 753 Environment.getExternalStorageState()); 754 } 755 756 /** 757 * Verify that shared storage lives on same volume as app. 758 */ 759 @Test testPrimaryOnSameVolume()760 public void testPrimaryOnSameVolume() throws Exception { 761 final File current = getContext().getFilesDir(); 762 final File primary = Environment.getExternalStorageDirectory(); 763 764 // Shared storage may jump through another filesystem for permission 765 // enforcement, so we verify that total/free space are identical. 766 final long totalDelta = Math.abs(current.getTotalSpace() - primary.getTotalSpace()); 767 final long freeDelta = Math.abs(current.getFreeSpace() - primary.getFreeSpace()); 768 if (totalDelta > MB_IN_BYTES * 300 || freeDelta > MB_IN_BYTES * 300) { 769 fail("Expected primary storage to be on same volume as app"); 770 } 771 } 772 773 @Test testCodeCacheWrite()774 public void testCodeCacheWrite() throws Exception { 775 assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile()); 776 assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile()); 777 } 778 779 @Test testCodeCacheRead()780 public void testCodeCacheRead() throws Exception { 781 assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists()); 782 assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists()); 783 } 784 785 @Test testRevision0_0()786 public void testRevision0_0() throws Exception { 787 final PackageInfo info = getContext().getPackageManager() 788 .getPackageInfo(getContext().getPackageName(), 0); 789 assertEquals(0, info.baseRevisionCode); 790 assertEquals(1, info.splitRevisionCodes.length); 791 assertEquals(0, info.splitRevisionCodes[0]); 792 } 793 794 @Test testRevision12_0()795 public void testRevision12_0() throws Exception { 796 final PackageInfo info = getContext().getPackageManager() 797 .getPackageInfo(getContext().getPackageName(), 0); 798 assertEquals(12, info.baseRevisionCode); 799 assertEquals(1, info.splitRevisionCodes.length); 800 assertEquals(0, info.splitRevisionCodes[0]); 801 } 802 803 @Test testRevision0_12()804 public void testRevision0_12() throws Exception { 805 final PackageInfo info = getContext().getPackageManager() 806 .getPackageInfo(getContext().getPackageName(), 0); 807 assertEquals(0, info.baseRevisionCode); 808 assertEquals(1, info.splitRevisionCodes.length); 809 assertEquals(12, info.splitRevisionCodes[0]); 810 } 811 812 @Test testComponentWithSplitName_singleBase()813 public void testComponentWithSplitName_singleBase() { 814 final PackageManager pm = getContext().getPackageManager(); 815 final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST"); 816 intent.setPackage(PKG); 817 818 // Service with split name `feature_warm` cannot be found 819 List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0); 820 assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo) 821 .equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue(); 822 823 // Provider with split name `feature_warm` cannot be found 824 resolveInfoList = pm.queryIntentContentProviders(intent, 0); 825 assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo) 826 .equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue(); 827 } 828 829 @Test testComponentWithSplitName_featureWarmInstalled()830 public void testComponentWithSplitName_featureWarmInstalled() throws Exception { 831 final PackageManager pm = getContext().getPackageManager(); 832 final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST"); 833 intent.setPackage(PKG); 834 835 // Service with split name `feature_warm` could be found 836 List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0); 837 assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo) 838 .equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue(); 839 840 // Provider with split name `feature_warm` could be found 841 resolveInfoList = pm.queryIntentContentProviders(intent, 0); 842 assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo) 843 .equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue(); 844 845 // And assert that we spun up the provider in this process 846 final Class<?> provider = Class.forName(FEATURE_WARM_EMPTY_PROVIDER_NAME.getClassName()); 847 final Field field = provider.getDeclaredField("sCreated"); 848 assertThat((boolean) field.get(null)).isTrue(); 849 } 850 851 @Test launchBaseActivity_withThemeBase_baseApplied()852 public void launchBaseActivity_withThemeBase_baseApplied() { 853 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base, 854 ThemeColors.BASE); 855 } 856 857 @Test launchBaseActivity_withThemeBaseLt_baseLtApplied()858 public void launchBaseActivity_withThemeBaseLt_baseLtApplied() { 859 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base, 860 ThemeColors.BASE_LT); 861 } 862 863 @Test launchBaseActivity_withThemeWarm_warmApplied()864 public void launchBaseActivity_withThemeWarm_warmApplied() { 865 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, 866 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM); 867 } 868 869 @Test launchBaseActivity_withThemeWarmLt_warmLtApplied()870 public void launchBaseActivity_withThemeWarmLt_warmLtApplied() { 871 assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, 872 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT); 873 } 874 875 @Test launchWarmActivity_withThemeBase_baseApplied()876 public void launchWarmActivity_withThemeBase_baseApplied() { 877 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base, 878 ThemeColors.BASE); 879 } 880 881 @Test launchWarmActivity_withThemeBaseLt_baseLtApplied()882 public void launchWarmActivity_withThemeBaseLt_baseLtApplied() { 883 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base, 884 ThemeColors.BASE_LT); 885 } 886 887 @Test launchWarmActivity_withThemeWarm_warmApplied()888 public void launchWarmActivity_withThemeWarm_warmApplied() { 889 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 890 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM); 891 } 892 893 @Test launchWarmActivity_withThemeWarmLt_warmLtApplied()894 public void launchWarmActivity_withThemeWarmLt_warmLtApplied() { 895 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 896 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT); 897 } 898 899 @Test launchWarmActivity_withThemeRose_roseApplied()900 public void launchWarmActivity_withThemeRose_roseApplied() { 901 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 902 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE); 903 } 904 905 @Test launchWarmActivity_withThemeRoseLt_roseLtApplied()906 public void launchWarmActivity_withThemeRoseLt_roseLtApplied() { 907 assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, 908 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT); 909 } 910 911 @Test launchRoseActivity_withThemeWarm_warmApplied()912 public void launchRoseActivity_withThemeWarm_warmApplied() { 913 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 914 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM); 915 } 916 917 @Test launchRoseActivity_withThemeWarmLt_warmLtApplied()918 public void launchRoseActivity_withThemeWarmLt_warmLtApplied() { 919 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 920 resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT); 921 } 922 923 @Test launchRoseActivity_withThemeRose_roseApplied()924 public void launchRoseActivity_withThemeRose_roseApplied() { 925 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 926 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE); 927 } 928 929 @Test launchRoseActivity_withThemeRoseLt_roseLtApplied()930 public void launchRoseActivity_withThemeRoseLt_roseLtApplied() { 931 assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY, 932 resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT); 933 } 934 assertActivityLaunchedAndThemeApplied(String activityName, int themeResId, ThemeColors themeColors)935 private void assertActivityLaunchedAndThemeApplied(String activityName, int themeResId, 936 ThemeColors themeColors) { 937 final Activity activity = mActivityRule.launchActivity( 938 getTestThemeIntent(activityName, themeResId)); 939 final TestThemeHelper expected = new TestThemeHelper(activity, themeResId); 940 expected.assertThemeValues(themeColors); 941 expected.assertThemeApplied(activity); 942 } 943 getContext()944 private static Context getContext() { 945 return InstrumentationRegistry.getInstrumentation().getTargetContext(); 946 } 947 updateDpi(Resources r, int densityDpi)948 private static void updateDpi(Resources r, int densityDpi) { 949 final Configuration c = new Configuration(r.getConfiguration()); 950 c.densityDpi = densityDpi; 951 r.updateConfiguration(c, r.getDisplayMetrics()); 952 } 953 updateLocale(Resources r, Locale locale)954 private static void updateLocale(Resources r, Locale locale) { 955 final Configuration c = new Configuration(r.getConfiguration()); 956 c.locale = locale; 957 r.updateConfiguration(c, r.getDisplayMetrics()); 958 } 959 getDrawableColor(Drawable d)960 private static int getDrawableColor(Drawable d) { 961 final Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), 962 Bitmap.Config.ARGB_8888); 963 final Canvas canvas = new Canvas(bitmap); 964 d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 965 d.draw(canvas); 966 return bitmap.getPixel(0, 0); 967 } 968 getXmlTestValue(XmlPullParser in)969 private static String getXmlTestValue(XmlPullParser in) throws XmlPullParserException, 970 IOException { 971 int type; 972 while ((type = in.next()) != END_DOCUMENT) { 973 if (type == START_TAG) { 974 final String tag = in.getName(); 975 if ("tag".equals(tag)) { 976 return in.getAttributeValue(null, "value"); 977 } 978 } 979 } 980 return null; 981 } 982 assertAssetContents(Resources r, String path, String expected)983 private static void assertAssetContents(Resources r, String path, String expected) 984 throws IOException { 985 BufferedReader in = null; 986 try { 987 in = new BufferedReader(new InputStreamReader(r.getAssets().open(path))); 988 assertEquals(expected, in.readLine()); 989 } finally { 990 if (in != null) in.close(); 991 } 992 } 993 writeString(File file, String value)994 private static void writeString(File file, String value) throws IOException { 995 final DataOutputStream os = new DataOutputStream(new FileOutputStream(file)); 996 try { 997 os.writeUTF(value); 998 } finally { 999 os.close(); 1000 } 1001 } 1002 readString(File file)1003 private static String readString(File file) throws IOException { 1004 final DataInputStream is = new DataInputStream(new FileInputStream(file)); 1005 try { 1006 return is.readUTF(); 1007 } finally { 1008 is.close(); 1009 } 1010 } 1011 resolveResourceId(String nameOfIdentifier)1012 private int resolveResourceId(String nameOfIdentifier) { 1013 final int resId = getContext().getResources().getIdentifier(nameOfIdentifier, null, null); 1014 assertTrue("Resource not found: " + nameOfIdentifier, resId != 0); 1015 return resId; 1016 } 1017 getTestThemeIntent(String activityName, int themeResId)1018 private static Intent getTestThemeIntent(String activityName, int themeResId) { 1019 final Intent intent = new Intent(ThemeActivity.INTENT_THEME_TEST); 1020 intent.setComponent(ComponentName.createRelative(PKG, activityName)); 1021 intent.putExtra(ThemeActivity.EXTRAS_THEME_RES_ID, themeResId); 1022 return intent; 1023 } 1024 getComponentName(ResolveInfo resolveInfo)1025 private static ComponentName getComponentName(ResolveInfo resolveInfo) { 1026 final ComponentInfo componentInfo = resolveInfo.activityInfo != null 1027 ? resolveInfo.activityInfo : resolveInfo.serviceInfo != null 1028 ? resolveInfo.serviceInfo : resolveInfo.providerInfo; 1029 if (componentInfo == null) { 1030 throw new AssertionError("Missing ComponentInfo in the ResolveInfo!"); 1031 } 1032 return new ComponentName(componentInfo.packageName, componentInfo.name); 1033 } 1034 setAppLinksUserSelection(String packageName, String uriHostName, boolean enabled)1035 private static void setAppLinksUserSelection(String packageName, String uriHostName, 1036 boolean enabled) { 1037 final String cmd = String.format("pm set-app-links-user-selection --user cur --package " 1038 + "%s %b %s", packageName, enabled, uriHostName); 1039 SystemUtil.runShellCommand(cmd); 1040 } 1041 } 1042