blob: 2308b983f19cc0dad60d2517da825bde780f2e57 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.splitapp;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNotSame;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.Environment;
import android.system.Os;
import android.system.StructStat;
import android.test.MoreAsserts;
import android.util.DisplayMetrics;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.cts.splitapp.TestThemeHelper.ThemeColors;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public class SplitAppTest {
private static final String TAG = "SplitAppTest";
private static final String PKG = "com.android.cts.splitapp";
private static final long MB_IN_BYTES = 1 * 1024 * 1024;
public static boolean sFeatureTouched = false;
public static String sFeatureValue = null;
private static final String BASE_THEME_ACTIVITY = ".ThemeActivity";
private static final String WARM_THEME_ACTIVITY = ".WarmThemeActivity";
private static final String ROSE_THEME_ACTIVITY = ".RoseThemeActivity";
private static final ComponentName FEATURE_WARM_EMPTY_PROVIDER_NAME =
ComponentName.createRelative(PKG, ".feature.warm.EmptyProvider");
private static final ComponentName FEATURE_WARM_EMPTY_SERVICE_NAME =
ComponentName.createRelative(PKG, ".feature.warm.EmptyService");
@Rule
public ActivityTestRule<Activity> mActivityRule =
new ActivityTestRule<>(Activity.class, true /*initialTouchMode*/,
false /*launchActivity*/);
@Test
public void testNothing() throws Exception {
}
@Test
public void testSingleBase() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
// Should have untouched resources from base
assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
assertEquals("blue", r.getString(R.string.my_string1));
assertEquals("purple", r.getString(R.string.my_string2));
assertEquals(0xff00ff00, r.getColor(R.color.my_color));
assertEquals(123, r.getInteger(R.integer.my_integer));
assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta)));
// We know about drawable IDs, but they're stripped from base
try {
r.getDrawable(R.drawable.image);
fail("Unexpected drawable in base");
} catch (Resources.NotFoundException expected) {
}
// Should have base assets
assertAssetContents(r, "file1.txt", "FILE1");
assertAssetContents(r, "dir/dirfile1.txt", "DIRFILE1");
try {
assertAssetContents(r, "file2.txt", null);
fail("Unexpected asset file2");
} catch (IOException expected) {
}
// Should only have base manifest items
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage(PKG);
List<ResolveInfo> result = pm.queryIntentActivities(intent, 0);
assertEquals(1, result.size());
assertEquals("com.android.cts.splitapp.MyActivity", result.get(0).activityInfo.name);
// Activity with split name `feature_warm` cannot be found.
intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
intent.setPackage(PKG);
assertThat(pm.queryIntentActivities(intent, 0).stream().noneMatch(
info -> info.activityInfo.name.equals(
"com.android.cts.splitapp.feature.warm.EmptyActivity"))).isTrue();
// Receiver disabled by default in base
intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.setPackage(PKG);
result = pm.queryBroadcastReceivers(intent, 0);
assertEquals(0, result.size());
// We shouldn't have any native code in base
try {
Native.add(2, 4);
fail("Unexpected native code in base");
} catch (UnsatisfiedLinkError expected) {
}
}
@Test
public void testDensitySingle() throws Exception {
final Resources r = getContext().getResources();
// We should still have base resources
assertEquals("blue", r.getString(R.string.my_string1));
assertEquals("purple", r.getString(R.string.my_string2));
// Now we know about drawables, but only mdpi
final Drawable d = r.getDrawable(R.drawable.image);
assertEquals(0xff7e00ff, getDrawableColor(d));
}
@Test
public void testDensityAll() throws Exception {
final Resources r = getContext().getResources();
// We should still have base resources
assertEquals("blue", r.getString(R.string.my_string1));
assertEquals("purple", r.getString(R.string.my_string2));
// Pretend that we're at each density
updateDpi(r, DisplayMetrics.DENSITY_MEDIUM);
assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image)));
updateDpi(r, DisplayMetrics.DENSITY_HIGH);
assertEquals(0xff00fcff, getDrawableColor(r.getDrawable(R.drawable.image)));
updateDpi(r, DisplayMetrics.DENSITY_XHIGH);
assertEquals(0xff80ff00, getDrawableColor(r.getDrawable(R.drawable.image)));
updateDpi(r, DisplayMetrics.DENSITY_XXHIGH);
assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
}
@Test
public void testDensityBest1() throws Exception {
final Resources r = getContext().getResources();
// Pretend that we're really high density, but we only have mdpi installed
updateDpi(r, DisplayMetrics.DENSITY_XXHIGH);
assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image)));
}
@Test
public void testDensityBest2() throws Exception {
final Resources r = getContext().getResources();
// Pretend that we're really high density, and now we have better match
updateDpi(r, DisplayMetrics.DENSITY_XXHIGH);
assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
}
@Test
public void testApi() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
// We should have updated boolean, different from base
assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled));
// Receiver should be enabled now
Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.setPackage(PKG);
List<ResolveInfo> result = pm.queryBroadcastReceivers(intent, 0);
assertEquals(1, result.size());
assertEquals("com.android.cts.splitapp.MyReceiver", result.get(0).activityInfo.name);
}
@Test
public void testLocale() throws Exception {
final Resources r = getContext().getResources();
updateLocale(r, Locale.ENGLISH);
assertEquals("blue", r.getString(R.string.my_string1));
assertEquals("purple", r.getString(R.string.my_string2));
updateLocale(r, Locale.GERMAN);
assertEquals("blau", r.getString(R.string.my_string1));
assertEquals("purple", r.getString(R.string.my_string2));
updateLocale(r, Locale.FRENCH);
assertEquals("blue", r.getString(R.string.my_string1));
assertEquals("pourpre", r.getString(R.string.my_string2));
}
@Test
public void testNative() throws Exception {
Log.d(TAG, "testNative() thinks it's using ABI " + Native.arch());
// Make sure we can do the maths
assertEquals(11642, Native.add(4933, 6709));
}
@Test
public void testNativeRevision_sub_shouldImplementBadly() throws Exception {
assertNotSame(1, Native.sub(0, -1));
}
@Test
public void testNativeRevision_sub_shouldImplementWell() throws Exception {
assertEquals(1, Native.sub(0, -1));
}
@Test
public void testNative64Bit() throws Exception {
Log.d(TAG, "The device supports 32Bit ABIs \""
+ Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+ Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
assertThat(Native.getAbiBitness()).isEqualTo(64);
}
@Test
public void testNative32Bit() throws Exception {
Log.d(TAG, "The device supports 32Bit ABIs \""
+ Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+ Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
assertThat(Native.getAbiBitness()).isEqualTo(32);
}
@Test
public void testNative_getNumberADirectly_shouldBeSeven() throws Exception {
assertThat(Native.getNumberADirectly()).isEqualTo(7);
}
@Test
public void testNative_getNumberAViaProxy_shouldBeSeven() throws Exception {
assertThat(Native.getNumberAViaProxy()).isEqualTo(7);
}
@Test
public void testNative_getNumberBDirectly_shouldBeEleven() throws Exception {
assertThat(Native.getNumberBDirectly()).isEqualTo(11);
}
@Test
public void testNative_getNumberBViaProxy_shouldBeEleven() throws Exception {
assertThat(Native.getNumberBViaProxy()).isEqualTo(11);
}
@Test
public void testFeatureWarmBase() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
// Should have untouched resources from base
assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
assertEquals("blue", r.getString(R.string.my_string1));
assertEquals("purple", r.getString(R.string.my_string2));
assertEquals(0xff00ff00, r.getColor(R.color.my_color));
assertEquals(123, r.getInteger(R.integer.my_integer));
assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta)));
// And that we can access resources from feature
assertEquals("red", r.getString(r.getIdentifier(
"com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
assertEquals(123, r.getInteger(r.getIdentifier(
"com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
final int intId = (int) featR.getDeclaredField("feature_integer").get(null);
final int stringId = (int) featR.getDeclaredField("feature_string").get(null);
assertEquals(true, r.getBoolean(boolId));
assertEquals(123, r.getInteger(intId));
assertEquals("red", r.getString(stringId));
// Should have both base and feature assets
assertAssetContents(r, "file1.txt", "FILE1");
assertAssetContents(r, "file2.txt", "FILE2");
assertAssetContents(r, "dir/dirfile1.txt", "DIRFILE1");
assertAssetContents(r, "dir/dirfile2.txt", "DIRFILE2");
// Should have both base and feature components
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage(PKG);
List<ResolveInfo> result = pm.queryIntentActivities(intent, 0);
assertEquals(2, result.size());
assertEquals("com.android.cts.splitapp.MyActivity", result.get(0).activityInfo.name);
assertEquals("com.android.cts.splitapp.FeatureActivity", result.get(1).activityInfo.name);
// Receiver only enabled in feature
intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.setPackage(PKG);
result = pm.queryBroadcastReceivers(intent, 0);
assertEquals(1, result.size());
assertEquals("com.android.cts.splitapp.FeatureReceiver", result.get(0).activityInfo.name);
// And we should have a service
intent = new Intent("com.android.cts.splitapp.service");
intent.setPackage(PKG);
result = pm.queryIntentServices(intent, 0);
assertEquals(1, result.size());
assertEquals("com.android.cts.splitapp.FeatureService", result.get(0).serviceInfo.name);
// And a provider too
ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp.provider", 0);
assertEquals("com.android.cts.splitapp.FeatureProvider", info.name);
// And assert that we spun up the provider in this process
final Class<?> provider = Class.forName("com.android.cts.splitapp.FeatureProvider");
final Field field = provider.getDeclaredField("sCreated");
assertTrue("Expected provider to have been created", (boolean) field.get(null));
assertTrue("Expected provider to have touched us", sFeatureTouched);
assertEquals(r.getString(R.string.my_string1), sFeatureValue);
// Finally ensure that we can execute some code from split
final Class<?> logic = Class.forName("com.android.cts.splitapp.FeatureLogic");
final Method method = logic.getDeclaredMethod("mult", new Class[] {
Integer.TYPE, Integer.TYPE });
assertEquals(72, (int) method.invoke(null, 12, 6));
// Make sure we didn't get an extra flag from feature split
assertTrue("Someone parsed application flag!",
(getContext().getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) == 0);
// Make sure we have permission from base APK
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null);
try {
// But no new permissions from the feature APK
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, null);
fail("Whaaa, we somehow gained permission from feature?");
} catch (SecurityException expected) {
}
// Assert that activity declared in the base can be found after feature_warm installed
intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
intent.setPackage(PKG);
assertThat(pm.queryIntentActivities(intent, 0).stream().anyMatch(
resolveInfo -> resolveInfo.activityInfo.name.equals(
"com.android.cts.splitapp.feature.warm.EmptyActivity"))).isTrue();
}
private Intent createLaunchIntent() {
final boolean isInstant = Boolean.parseBoolean(
InstrumentationRegistry.getArguments().getString("is_instant", "false"));
if (isInstant) {
final Intent i = new Intent(Intent.ACTION_VIEW);
i.addCategory(Intent.CATEGORY_BROWSABLE);
i.setData(Uri.parse("https://cts.android.com/norestart"));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return i;
} else {
final Intent i = new Intent("com.android.cts.norestart.START");
i.addCategory(Intent.CATEGORY_DEFAULT);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return i;
}
}
@Test
public void testBaseInstalled() throws Exception {
final ConditionVariable cv = new ConditionVariable();
final BroadcastReceiver r = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
assertEquals(0, intent.getIntExtra("NEW_INTENT_COUNT", -1));
assertNull(intent.getStringExtra("RESOURCE_CONTENT"));
cv.open();
}
};
final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
getContext().registerReceiver(r, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
final Intent i = createLaunchIntent();
getContext().startActivity(i);
assertTrue(cv.block(2000L));
getContext().unregisterReceiver(r);
}
/**
* Tests a running activity remains active while a new feature split is installed.
* <p>
* Prior to running this test, the activity must be started. That is currently
* done in {@link #testBaseInstalled()}.
*/
@Test
public void testFeatureInstalled() throws Exception {
final ConditionVariable cv = new ConditionVariable();
final BroadcastReceiver r = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
assertEquals(1, intent.getIntExtra("NEW_INTENT_COUNT", -1));
assertEquals("Hello feature!", intent.getStringExtra("RESOURCE_CONTENT"));
cv.open();
}
};
final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
getContext().registerReceiver(r, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
final Intent i = createLaunchIntent();
getContext().startActivity(i);
assertTrue(cv.block(2000L));
getContext().unregisterReceiver(r);
}
@Test
public void testFeatureWarmApi() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
// Should have untouched resources from base
assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
// And that we can access resources from feature
assertEquals(321, r.getInteger(r.getIdentifier(
"com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
final int intId = (int) featR.getDeclaredField("feature_integer").get(null);
final int stringId = (int) featR.getDeclaredField("feature_string").get(null);
assertEquals(false, r.getBoolean(boolId));
assertEquals(321, r.getInteger(intId));
assertEquals("red", r.getString(stringId));
// And now both receivers should be disabled
Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.setPackage(PKG);
List<ResolveInfo> result = pm.queryBroadcastReceivers(intent, 0);
assertEquals(0, result.size());
}
@Test
public void testInheritUpdatedBase_withRevisionA() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
// Resources should have been updated
assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled));
assertEquals("blue-revision", r.getString(R.string.my_string1));
assertEquals("purple-revision", r.getString(R.string.my_string2));
assertEquals(0xff00ffff, r.getColor(R.color.my_color));
assertEquals(456, r.getInteger(R.integer.my_integer));
// Also, new resources could be found
assertEquals("new string", r.getString(r.getIdentifier(
"my_new_string", "string", PKG)));
assertAssetContents(r, "fileA.txt", "FILEA");
assertAssetContents(r, "dir/dirfileA.txt", "DIRFILEA");
// Activity of ACTION_MAIN should have been updated to .revision_a.MyActivity
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage(PKG);
final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
.map(info -> info.activityInfo.name).collect(Collectors.toList());
assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.MyActivity");
// Receiver of DATE_CHANGED should have been updated to .revision_a.MyReceiver
intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.setPackage(PKG);
final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
.map(info -> info.activityInfo.name).collect(Collectors.toList());
assertThat(receiverNames).contains("com.android.cts.splitapp.revision_a.MyReceiver");
// Provider should have been updated to .revision_a.MyProvider
final ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp", 0);
assertEquals("com.android.cts.splitapp.revision_a.MyProvider", info.name);
// And assert that we spun up the provider in this process
final Class<?> provider = Class.forName("com.android.cts.splitapp.revision_a.MyProvider");
final Field field = provider.getDeclaredField("sCreated");
assertTrue("Expected provider to have been created", (boolean) field.get(null));
// Camera permission has been removed
try {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null);
fail("Camera permission should not be granted");
} catch (SecurityException expected) {
}
// New Vibrate permision should be granted
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, null);
}
@Test
public void testInheritUpdatedSplit_withRevisionA() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
// Resources should have been updated
assertEquals("red-revision", r.getString(r.getIdentifier(
"com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
assertEquals(456, r.getInteger(r.getIdentifier(
"com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
// Also, new resources could be found
assertEquals("feature new string", r.getString(r.getIdentifier(
"com.android.cts.splitapp.feature_warm:feature_new_string", "string", PKG)));
assertAssetContents(r, "fileFA.txt", "FILE_FA");
assertAssetContents(r, "dir/dirfileFA.txt", "DIRFILE_FA");
// Activity of ACTION_MAIN should have been updated to .revision_a.FeatureActivity
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage(PKG);
final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
.map(info -> info.activityInfo.name).collect(Collectors.toList());
assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.FeatureActivity");
// Receiver of DATE_CHANGED could not be found
intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.setPackage(PKG);
final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
.map(info -> info.activityInfo.name).collect(Collectors.toList());
assertThat(receiverNames).doesNotContain("com.android.cts.splitapp.FeatureReceiver");
// Service of splitapp should have been updated to .revision_a.FeatureService
intent = new Intent("com.android.cts.splitapp.service");
intent.setPackage(PKG);
final List<String> serviceNames = pm.queryIntentServices(intent, 0).stream()
.map(info -> info.serviceInfo.name).collect(Collectors.toList());
assertThat(serviceNames).contains("com.android.cts.splitapp.revision_a.FeatureService");
// Provider should have been updated to .revision_a.FeatureProvider
final ProviderInfo info = pm.resolveContentProvider(
"com.android.cts.splitapp.provider", 0);
assertEquals("com.android.cts.splitapp.revision_a.FeatureProvider", info.name);
// And assert that we spun up the provider in this process
final Class<?> provider = Class.forName(
"com.android.cts.splitapp.revision_a.FeatureProvider");
final Field field = provider.getDeclaredField("sCreated");
assertTrue("Expected provider to have been created", (boolean) field.get(null));
}
/**
* Write app data in a number of locations that expect to remain intact over
* long periods of time, such as across app moves.
*/
@Test
public void testDataWrite() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
writeString(getContext().getFileStreamPath("my_int"), token);
final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
Context.MODE_PRIVATE, null);
try {
db.execSQL("DROP TABLE IF EXISTS my_table");
db.execSQL("CREATE TABLE my_table(value INTEGER)");
db.execSQL("INSERT INTO my_table VALUES (101), (102), (103)");
} finally {
db.close();
}
}
/**
* Verify that data written by {@link #testDataWrite()} is still intact.
*/
@Test
public void testDataRead() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
assertEquals(token, readString(getContext().getFileStreamPath("my_int")));
final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
Context.MODE_PRIVATE, null);
try {
final Cursor cursor = db.query("my_table", null, null, null, null, null, "value ASC");
try {
assertEquals(3, cursor.getCount());
assertTrue(cursor.moveToPosition(0));
assertEquals(101, cursor.getInt(0));
assertTrue(cursor.moveToPosition(1));
assertEquals(102, cursor.getInt(0));
assertTrue(cursor.moveToPosition(2));
assertEquals(103, cursor.getInt(0));
} finally {
cursor.close();
}
} finally {
db.close();
}
}
/**
* Verify that app is installed on internal storage.
*/
@Test
public void testDataInternal() throws Exception {
final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
assertEquals(internal.st_dev, actual.st_dev);
}
/**
* Verify that app is not installed on internal storage.
*/
@Test
public void testDataNotInternal() throws Exception {
final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev);
}
@Test
public void testPrimaryDataWrite() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token);
}
@Test
public void testPrimaryDataRead() throws Exception {
final String token = String.valueOf(android.os.Process.myUid());
assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext")));
}
/**
* Verify shared storage behavior when on internal storage.
*/
@Test
public void testPrimaryInternal() throws Exception {
assertTrue("emulated", Environment.isExternalStorageEmulated());
assertFalse("removable", Environment.isExternalStorageRemovable());
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
}
/**
* Verify shared storage behavior when on physical storage.
*/
@Test
public void testPrimaryPhysical() throws Exception {
assertFalse("emulated", Environment.isExternalStorageEmulated());
assertTrue("removable", Environment.isExternalStorageRemovable());
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
}
/**
* Verify shared storage behavior when on adopted storage.
*/
@Test
public void testPrimaryAdopted() throws Exception {
assertTrue("emulated", Environment.isExternalStorageEmulated());
assertTrue("removable", Environment.isExternalStorageRemovable());
assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
}
/**
* Verify that shared storage is unmounted.
*/
@Test
public void testPrimaryUnmounted() throws Exception {
MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED,
Environment.getExternalStorageState());
}
/**
* Verify that shared storage lives on same volume as app.
*/
@Test
public void testPrimaryOnSameVolume() throws Exception {
final File current = getContext().getFilesDir();
final File primary = Environment.getExternalStorageDirectory();
// Shared storage may jump through another filesystem for permission
// enforcement, so we verify that total/free space are identical.
final long totalDelta = Math.abs(current.getTotalSpace() - primary.getTotalSpace());
final long freeDelta = Math.abs(current.getFreeSpace() - primary.getFreeSpace());
if (totalDelta > MB_IN_BYTES * 300 || freeDelta > MB_IN_BYTES * 300) {
fail("Expected primary storage to be on same volume as app");
}
}
@Test
public void testCodeCacheWrite() throws Exception {
assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
}
@Test
public void testCodeCacheRead() throws Exception {
assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists());
assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists());
}
@Test
public void testRevision0_0() throws Exception {
final PackageInfo info = getContext().getPackageManager()
.getPackageInfo(getContext().getPackageName(), 0);
assertEquals(0, info.baseRevisionCode);
assertEquals(1, info.splitRevisionCodes.length);
assertEquals(0, info.splitRevisionCodes[0]);
}
@Test
public void testRevision12_0() throws Exception {
final PackageInfo info = getContext().getPackageManager()
.getPackageInfo(getContext().getPackageName(), 0);
assertEquals(12, info.baseRevisionCode);
assertEquals(1, info.splitRevisionCodes.length);
assertEquals(0, info.splitRevisionCodes[0]);
}
@Test
public void testRevision0_12() throws Exception {
final PackageInfo info = getContext().getPackageManager()
.getPackageInfo(getContext().getPackageName(), 0);
assertEquals(0, info.baseRevisionCode);
assertEquals(1, info.splitRevisionCodes.length);
assertEquals(12, info.splitRevisionCodes[0]);
}
@Test
public void testComponentWithSplitName_singleBase() {
final PackageManager pm = getContext().getPackageManager();
final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
intent.setPackage(PKG);
// Service with split name `feature_warm` cannot be found
List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0);
assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo)
.equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue();
// Provider with split name `feature_warm` cannot be found
resolveInfoList = pm.queryIntentContentProviders(intent, 0);
assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo)
.equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue();
}
@Test
public void testComponentWithSplitName_featureWarmInstalled() throws Exception {
final PackageManager pm = getContext().getPackageManager();
final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
intent.setPackage(PKG);
// Service with split name `feature_warm` could be found
List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0);
assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo)
.equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue();
// Provider with split name `feature_warm` could be found
resolveInfoList = pm.queryIntentContentProviders(intent, 0);
assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo)
.equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue();
// And assert that we spun up the provider in this process
final Class<?> provider = Class.forName(FEATURE_WARM_EMPTY_PROVIDER_NAME.getClassName());
final Field field = provider.getDeclaredField("sCreated");
assertThat((boolean) field.get(null)).isTrue();
}
@Test
public void launchBaseActivity_withThemeBase_baseApplied() {
assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
ThemeColors.BASE);
}
@Test
public void launchBaseActivity_withThemeBaseLt_baseLtApplied() {
assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
ThemeColors.BASE_LT);
}
@Test
public void launchBaseActivity_withThemeWarm_warmApplied() {
assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
}
@Test
public void launchBaseActivity_withThemeWarmLt_warmLtApplied() {
assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
}
@Test
public void launchWarmActivity_withThemeBase_baseApplied() {
assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
ThemeColors.BASE);
}
@Test
public void launchWarmActivity_withThemeBaseLt_baseLtApplied() {
assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
ThemeColors.BASE_LT);
}
@Test
public void launchWarmActivity_withThemeWarm_warmApplied() {
assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
}
@Test
public void launchWarmActivity_withThemeWarmLt_warmLtApplied() {
assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
}
@Test
public void launchWarmActivity_withThemeRose_roseApplied() {
assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
}
@Test
public void launchWarmActivity_withThemeRoseLt_roseLtApplied() {
assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
}
@Test
public void launchRoseActivity_withThemeWarm_warmApplied() {
assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
}
@Test
public void launchRoseActivity_withThemeWarmLt_warmLtApplied() {
assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
}
@Test
public void launchRoseActivity_withThemeRose_roseApplied() {
assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
}
@Test
public void launchRoseActivity_withThemeRoseLt_roseLtApplied() {
assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
}
private void assertActivityLaunchedAndThemeApplied(String activityName, int themeResId,
ThemeColors themeColors) {
final Activity activity = mActivityRule.launchActivity(
getTestThemeIntent(activityName, themeResId));
final TestThemeHelper expected = new TestThemeHelper(activity, themeResId);
expected.assertThemeValues(themeColors);
expected.assertThemeApplied(activity);
}
private static Context getContext() {
return InstrumentationRegistry.getInstrumentation().getTargetContext();
}
private static void updateDpi(Resources r, int densityDpi) {
final Configuration c = new Configuration(r.getConfiguration());
c.densityDpi = densityDpi;
r.updateConfiguration(c, r.getDisplayMetrics());
}
private static void updateLocale(Resources r, Locale locale) {
final Configuration c = new Configuration(r.getConfiguration());
c.locale = locale;
r.updateConfiguration(c, r.getDisplayMetrics());
}
private static int getDrawableColor(Drawable d) {
final Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
d.draw(canvas);
return bitmap.getPixel(0, 0);
}
private static String getXmlTestValue(XmlPullParser in) throws XmlPullParserException,
IOException {
int type;
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
if ("tag".equals(tag)) {
return in.getAttributeValue(null, "value");
}
}
}
return null;
}
private static void assertAssetContents(Resources r, String path, String expected)
throws IOException {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(r.getAssets().open(path)));
assertEquals(expected, in.readLine());
} finally {
if (in != null) in.close();
}
}
private static void writeString(File file, String value) throws IOException {
final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
try {
os.writeUTF(value);
} finally {
os.close();
}
}
private static String readString(File file) throws IOException {
final DataInputStream is = new DataInputStream(new FileInputStream(file));
try {
return is.readUTF();
} finally {
is.close();
}
}
private int resolveResourceId(String nameOfIdentifier) {
final int resId = getContext().getResources().getIdentifier(nameOfIdentifier, null, null);
assertTrue("Resource not found: " + nameOfIdentifier, resId != 0);
return resId;
}
private static Intent getTestThemeIntent(String activityName, int themeResId) {
final Intent intent = new Intent(ThemeActivity.INTENT_THEME_TEST);
intent.setComponent(ComponentName.createRelative(PKG, activityName));
intent.putExtra(ThemeActivity.EXTRAS_THEME_RES_ID, themeResId);
return intent;
}
private static ComponentName getComponentName(ResolveInfo resolveInfo) {
final ComponentInfo componentInfo = resolveInfo.activityInfo != null
? resolveInfo.activityInfo : resolveInfo.serviceInfo != null
? resolveInfo.serviceInfo : resolveInfo.providerInfo;
if (componentInfo == null) {
throw new AssertionError("Missing ComponentInfo in the ResolveInfo!");
}
return new ComponentName(componentInfo.packageName, componentInfo.name);
}
}