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