Home | History | Annotate | Download | only in splitapp
      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 org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
     20 import static org.xmlpull.v1.XmlPullParser.START_TAG;
     21 
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.pm.ApplicationInfo;
     27 import android.content.pm.PackageInfo;
     28 import android.content.pm.PackageManager;
     29 import android.content.pm.ProviderInfo;
     30 import android.content.pm.ResolveInfo;
     31 import android.content.res.Configuration;
     32 import android.content.res.Resources;
     33 import android.database.Cursor;
     34 import android.database.sqlite.SQLiteDatabase;
     35 import android.graphics.Bitmap;
     36 import android.graphics.Canvas;
     37 import android.graphics.drawable.Drawable;
     38 import android.net.Uri;
     39 import android.os.ConditionVariable;
     40 import android.os.Environment;
     41 import android.system.Os;
     42 import android.system.StructStat;
     43 import android.test.AndroidTestCase;
     44 import android.test.MoreAsserts;
     45 import android.util.DisplayMetrics;
     46 import android.util.Log;
     47 
     48 import androidx.test.InstrumentationRegistry;
     49 
     50 import org.xmlpull.v1.XmlPullParser;
     51 import org.xmlpull.v1.XmlPullParserException;
     52 
     53 import java.io.BufferedReader;
     54 import java.io.DataInputStream;
     55 import java.io.DataOutputStream;
     56 import java.io.File;
     57 import java.io.FileInputStream;
     58 import java.io.FileOutputStream;
     59 import java.io.IOException;
     60 import java.io.InputStreamReader;
     61 import java.lang.reflect.Field;
     62 import java.lang.reflect.Method;
     63 import java.util.List;
     64 import java.util.Locale;
     65 
     66 public class SplitAppTest extends AndroidTestCase {
     67     private static final String TAG = "SplitAppTest";
     68     private static final String PKG = "com.android.cts.splitapp";
     69 
     70     private static final long MB_IN_BYTES = 1 * 1024 * 1024;
     71 
     72     public static boolean sFeatureTouched = false;
     73     public static String sFeatureValue = null;
     74 
     75     public void testNothing() throws Exception {
     76     }
     77 
     78     public void testSingleBase() throws Exception {
     79         final Resources r = getContext().getResources();
     80         final PackageManager pm = getContext().getPackageManager();
     81 
     82         // Should have untouched resources from base
     83         assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
     84 
     85         assertEquals("blue", r.getString(R.string.my_string1));
     86         assertEquals("purple", r.getString(R.string.my_string2));
     87 
     88         assertEquals(0xff00ff00, r.getColor(R.color.my_color));
     89         assertEquals(123, r.getInteger(R.integer.my_integer));
     90 
     91         assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta)));
     92 
     93         // We know about drawable IDs, but they're stripped from base
     94         try {
     95             r.getDrawable(R.drawable.image);
     96             fail("Unexpected drawable in base");
     97         } catch (Resources.NotFoundException expected) {
     98         }
     99 
    100         // Should have base assets
    101         assertAssetContents(r, "file1.txt", "FILE1");
    102         assertAssetContents(r, "dir/dirfile1.txt", "DIRFILE1");
    103 
    104         try {
    105             assertAssetContents(r, "file2.txt", null);
    106             fail("Unexpected asset file2");
    107         } catch (IOException expected) {
    108         }
    109 
    110         // Should only have base manifest items
    111         Intent intent = new Intent(Intent.ACTION_MAIN);
    112         intent.addCategory(Intent.CATEGORY_LAUNCHER);
    113         intent.setPackage(PKG);
    114 
    115         List<ResolveInfo> result = pm.queryIntentActivities(intent, 0);
    116         assertEquals(1, result.size());
    117         assertEquals("com.android.cts.splitapp.MyActivity", result.get(0).activityInfo.name);
    118 
    119         // Receiver disabled by default in base
    120         intent = new Intent(Intent.ACTION_DATE_CHANGED);
    121         intent.setPackage(PKG);
    122 
    123         result = pm.queryBroadcastReceivers(intent, 0);
    124         assertEquals(0, result.size());
    125 
    126         // We shouldn't have any native code in base
    127         try {
    128             Native.add(2, 4);
    129             fail("Unexpected native code in base");
    130         } catch (UnsatisfiedLinkError expected) {
    131         }
    132     }
    133 
    134     public void testDensitySingle() throws Exception {
    135         final Resources r = getContext().getResources();
    136 
    137         // We should still have base resources
    138         assertEquals("blue", r.getString(R.string.my_string1));
    139         assertEquals("purple", r.getString(R.string.my_string2));
    140 
    141         // Now we know about drawables, but only mdpi
    142         final Drawable d = r.getDrawable(R.drawable.image);
    143         assertEquals(0xff7e00ff, getDrawableColor(d));
    144     }
    145 
    146     public void testDensityAll() throws Exception {
    147         final Resources r = getContext().getResources();
    148 
    149         // We should still have base resources
    150         assertEquals("blue", r.getString(R.string.my_string1));
    151         assertEquals("purple", r.getString(R.string.my_string2));
    152 
    153         // Pretend that we're at each density
    154         updateDpi(r, DisplayMetrics.DENSITY_MEDIUM);
    155         assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image)));
    156 
    157         updateDpi(r, DisplayMetrics.DENSITY_HIGH);
    158         assertEquals(0xff00fcff, getDrawableColor(r.getDrawable(R.drawable.image)));
    159 
    160         updateDpi(r, DisplayMetrics.DENSITY_XHIGH);
    161         assertEquals(0xff80ff00, getDrawableColor(r.getDrawable(R.drawable.image)));
    162 
    163         updateDpi(r, DisplayMetrics.DENSITY_XXHIGH);
    164         assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
    165     }
    166 
    167     public void testDensityBest1() throws Exception {
    168         final Resources r = getContext().getResources();
    169 
    170         // Pretend that we're really high density, but we only have mdpi installed
    171         updateDpi(r, DisplayMetrics.DENSITY_XXHIGH);
    172         assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image)));
    173     }
    174 
    175     public void testDensityBest2() throws Exception {
    176         final Resources r = getContext().getResources();
    177 
    178         // Pretend that we're really high density, and now we have better match
    179         updateDpi(r, DisplayMetrics.DENSITY_XXHIGH);
    180         assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
    181     }
    182 
    183     public void testApi() throws Exception {
    184         final Resources r = getContext().getResources();
    185         final PackageManager pm = getContext().getPackageManager();
    186 
    187         // We should have updated boolean, different from base
    188         assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled));
    189 
    190         // Receiver should be enabled now
    191         Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
    192         intent.setPackage(PKG);
    193 
    194         List<ResolveInfo> result = pm.queryBroadcastReceivers(intent, 0);
    195         assertEquals(1, result.size());
    196         assertEquals("com.android.cts.splitapp.MyReceiver", result.get(0).activityInfo.name);
    197     }
    198 
    199     public void testLocale() throws Exception {
    200         final Resources r = getContext().getResources();
    201 
    202         updateLocale(r, Locale.ENGLISH);
    203         assertEquals("blue", r.getString(R.string.my_string1));
    204         assertEquals("purple", r.getString(R.string.my_string2));
    205 
    206         updateLocale(r, Locale.GERMAN);
    207         assertEquals("blau", r.getString(R.string.my_string1));
    208         assertEquals("purple", r.getString(R.string.my_string2));
    209 
    210         updateLocale(r, Locale.FRENCH);
    211         assertEquals("blue", r.getString(R.string.my_string1));
    212         assertEquals("pourpre", r.getString(R.string.my_string2));
    213     }
    214 
    215     public void testNative() throws Exception {
    216         Log.d(TAG, "testNative() thinks it's using ABI " + Native.arch());
    217 
    218         // Make sure we can do the maths
    219         assertEquals(11642, Native.add(4933, 6709));
    220     }
    221 
    222     public void testFeatureBase() throws Exception {
    223         final Resources r = getContext().getResources();
    224         final PackageManager pm = getContext().getPackageManager();
    225 
    226         // Should have untouched resources from base
    227         assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
    228 
    229         assertEquals("blue", r.getString(R.string.my_string1));
    230         assertEquals("purple", r.getString(R.string.my_string2));
    231 
    232         assertEquals(0xff00ff00, r.getColor(R.color.my_color));
    233         assertEquals(123, r.getInteger(R.integer.my_integer));
    234 
    235         assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta)));
    236 
    237         // And that we can access resources from feature
    238         assertEquals("red", r.getString(r.getIdentifier(
    239                 "com.android.cts.splitapp.feature:feature_string", "string", PKG)));
    240         assertEquals(123, r.getInteger(r.getIdentifier(
    241                 "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
    242 
    243         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
    244         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
    245         final int intId = (int) featR.getDeclaredField("feature_integer").get(null);
    246         final int stringId = (int) featR.getDeclaredField("feature_string").get(null);
    247         assertEquals(true, r.getBoolean(boolId));
    248         assertEquals(123, r.getInteger(intId));
    249         assertEquals("red", r.getString(stringId));
    250 
    251         // Should have both base and feature assets
    252         assertAssetContents(r, "file1.txt", "FILE1");
    253         assertAssetContents(r, "file2.txt", "FILE2");
    254         assertAssetContents(r, "dir/dirfile1.txt", "DIRFILE1");
    255         assertAssetContents(r, "dir/dirfile2.txt", "DIRFILE2");
    256 
    257         // Should have both base and feature components
    258         Intent intent = new Intent(Intent.ACTION_MAIN);
    259         intent.addCategory(Intent.CATEGORY_LAUNCHER);
    260         intent.setPackage(PKG);
    261         List<ResolveInfo> result = pm.queryIntentActivities(intent, 0);
    262         assertEquals(2, result.size());
    263         assertEquals("com.android.cts.splitapp.MyActivity", result.get(0).activityInfo.name);
    264         assertEquals("com.android.cts.splitapp.FeatureActivity", result.get(1).activityInfo.name);
    265 
    266         // Receiver only enabled in feature
    267         intent = new Intent(Intent.ACTION_DATE_CHANGED);
    268         intent.setPackage(PKG);
    269         result = pm.queryBroadcastReceivers(intent, 0);
    270         assertEquals(1, result.size());
    271         assertEquals("com.android.cts.splitapp.FeatureReceiver", result.get(0).activityInfo.name);
    272 
    273         // And we should have a service
    274         intent = new Intent("com.android.cts.splitapp.service");
    275         intent.setPackage(PKG);
    276         result = pm.queryIntentServices(intent, 0);
    277         assertEquals(1, result.size());
    278         assertEquals("com.android.cts.splitapp.FeatureService", result.get(0).serviceInfo.name);
    279 
    280         // And a provider too
    281         ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp.provider", 0);
    282         assertEquals("com.android.cts.splitapp.FeatureProvider", info.name);
    283 
    284         // And assert that we spun up the provider in this process
    285         final Class<?> provider = Class.forName("com.android.cts.splitapp.FeatureProvider");
    286         final Field field = provider.getDeclaredField("sCreated");
    287         assertTrue("Expected provider to have been created", (boolean) field.get(null));
    288         assertTrue("Expected provider to have touched us", sFeatureTouched);
    289         assertEquals(r.getString(R.string.my_string1), sFeatureValue);
    290 
    291         // Finally ensure that we can execute some code from split
    292         final Class<?> logic = Class.forName("com.android.cts.splitapp.FeatureLogic");
    293         final Method method = logic.getDeclaredMethod("mult", new Class[] {
    294                 Integer.TYPE, Integer.TYPE });
    295         assertEquals(72, (int) method.invoke(null, 12, 6));
    296 
    297         // Make sure we didn't get an extra flag from feature split
    298         assertTrue("Someone parsed application flag!",
    299                 (getContext().getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) == 0);
    300 
    301         // Make sure we have permission from base APK
    302         getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null);
    303 
    304         try {
    305             // But no new permissions from the feature APK
    306             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, null);
    307             fail("Whaaa, we somehow gained permission from feature?");
    308         } catch (SecurityException expected) {
    309         }
    310     }
    311 
    312     private Intent createLaunchIntent() {
    313         final boolean isInstant = Boolean.parseBoolean(
    314                 InstrumentationRegistry.getArguments().getString("is_instant", "false"));
    315         if (isInstant) {
    316             final Intent i = new Intent(Intent.ACTION_VIEW);
    317             i.addCategory(Intent.CATEGORY_BROWSABLE);
    318             i.setData(Uri.parse("https://cts.android.com/norestart"));
    319             i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    320             return i;
    321         } else {
    322             final Intent i = new Intent("com.android.cts.norestart.START");
    323             i.addCategory(Intent.CATEGORY_DEFAULT);
    324             i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    325             return i;
    326         }
    327     }
    328 
    329     public void testBaseInstalled() throws Exception {
    330         final ConditionVariable cv = new ConditionVariable();
    331         final BroadcastReceiver r = new BroadcastReceiver() {
    332             @Override
    333             public void onReceive(Context context, Intent intent) {
    334                 assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
    335                 assertEquals(0, intent.getIntExtra("NEW_INTENT_COUNT", -1));
    336                 cv.open();
    337             }
    338         };
    339         final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
    340         getContext().registerReceiver(r, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
    341         final Intent i = createLaunchIntent();
    342         getContext().startActivity(i);
    343         assertTrue(cv.block(2000L));
    344         getContext().unregisterReceiver(r);
    345     }
    346 
    347     /**
    348      * Tests a running activity remains active while a new feature split is installed.
    349      * <p>
    350      * Prior to running this test, the activity must be started. That is currently
    351      * done in {@link #testBaseInstalled()}.
    352      */
    353     public void testFeatureInstalled() throws Exception {
    354         final ConditionVariable cv = new ConditionVariable();
    355         final BroadcastReceiver r = new BroadcastReceiver() {
    356             @Override
    357             public void onReceive(Context context, Intent intent) {
    358                 assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
    359                 assertEquals(1, intent.getIntExtra("NEW_INTENT_COUNT", -1));
    360                 cv.open();
    361             }
    362         };
    363         final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
    364         getContext().registerReceiver(r, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
    365         final Intent i = createLaunchIntent();
    366         getContext().startActivity(i);
    367         assertTrue(cv.block(2000L));
    368         getContext().unregisterReceiver(r);
    369     }
    370 
    371     public void testFeatureApi() throws Exception {
    372         final Resources r = getContext().getResources();
    373         final PackageManager pm = getContext().getPackageManager();
    374 
    375         // Should have untouched resources from base
    376         assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
    377 
    378         // And that we can access resources from feature
    379         assertEquals(321, r.getInteger(r.getIdentifier(
    380                 "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
    381 
    382         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
    383         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
    384         final int intId = (int) featR.getDeclaredField("feature_integer").get(null);
    385         final int stringId = (int) featR.getDeclaredField("feature_string").get(null);
    386         assertEquals(false, r.getBoolean(boolId));
    387         assertEquals(321, r.getInteger(intId));
    388         assertEquals("red", r.getString(stringId));
    389 
    390         // And now both receivers should be disabled
    391         Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
    392         intent.setPackage(PKG);
    393         List<ResolveInfo> result = pm.queryBroadcastReceivers(intent, 0);
    394         assertEquals(0, result.size());
    395     }
    396 
    397     /**
    398      * Write app data in a number of locations that expect to remain intact over
    399      * long periods of time, such as across app moves.
    400      */
    401     public void testDataWrite() throws Exception {
    402         final String token = String.valueOf(android.os.Process.myUid());
    403         writeString(getContext().getFileStreamPath("my_int"), token);
    404 
    405         final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
    406                 Context.MODE_PRIVATE, null);
    407         try {
    408             db.execSQL("DROP TABLE IF EXISTS my_table");
    409             db.execSQL("CREATE TABLE my_table(value INTEGER)");
    410             db.execSQL("INSERT INTO my_table VALUES (101), (102), (103)");
    411         } finally {
    412             db.close();
    413         }
    414     }
    415 
    416     /**
    417      * Verify that data written by {@link #testDataWrite()} is still intact.
    418      */
    419     public void testDataRead() throws Exception {
    420         final String token = String.valueOf(android.os.Process.myUid());
    421         assertEquals(token, readString(getContext().getFileStreamPath("my_int")));
    422 
    423         final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
    424                 Context.MODE_PRIVATE, null);
    425         try {
    426             final Cursor cursor = db.query("my_table", null, null, null, null, null, "value ASC");
    427             try {
    428                 assertEquals(3, cursor.getCount());
    429                 assertTrue(cursor.moveToPosition(0));
    430                 assertEquals(101, cursor.getInt(0));
    431                 assertTrue(cursor.moveToPosition(1));
    432                 assertEquals(102, cursor.getInt(0));
    433                 assertTrue(cursor.moveToPosition(2));
    434                 assertEquals(103, cursor.getInt(0));
    435             } finally {
    436                 cursor.close();
    437             }
    438         } finally {
    439             db.close();
    440         }
    441     }
    442 
    443     /**
    444      * Verify that app is installed on internal storage.
    445      */
    446     public void testDataInternal() throws Exception {
    447         final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
    448         final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
    449         assertEquals(internal.st_dev, actual.st_dev);
    450     }
    451 
    452     /**
    453      * Verify that app is not installed on internal storage.
    454      */
    455     public void testDataNotInternal() throws Exception {
    456         final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
    457         final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
    458         MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev);
    459     }
    460 
    461     public void testPrimaryDataWrite() throws Exception {
    462         final String token = String.valueOf(android.os.Process.myUid());
    463         writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token);
    464     }
    465 
    466     public void testPrimaryDataRead() throws Exception {
    467         final String token = String.valueOf(android.os.Process.myUid());
    468         assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext")));
    469     }
    470 
    471     /**
    472      * Verify shared storage behavior when on internal storage.
    473      */
    474     public void testPrimaryInternal() throws Exception {
    475         assertTrue("emulated", Environment.isExternalStorageEmulated());
    476         assertFalse("removable", Environment.isExternalStorageRemovable());
    477         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
    478     }
    479 
    480     /**
    481      * Verify shared storage behavior when on physical storage.
    482      */
    483     public void testPrimaryPhysical() throws Exception {
    484         assertFalse("emulated", Environment.isExternalStorageEmulated());
    485         assertTrue("removable", Environment.isExternalStorageRemovable());
    486         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
    487     }
    488 
    489     /**
    490      * Verify shared storage behavior when on adopted storage.
    491      */
    492     public void testPrimaryAdopted() throws Exception {
    493         assertTrue("emulated", Environment.isExternalStorageEmulated());
    494         assertTrue("removable", Environment.isExternalStorageRemovable());
    495         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
    496     }
    497 
    498     /**
    499      * Verify that shared storage is unmounted.
    500      */
    501     public void testPrimaryUnmounted() throws Exception {
    502         MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED,
    503                 Environment.getExternalStorageState());
    504     }
    505 
    506     /**
    507      * Verify that shared storage lives on same volume as app.
    508      */
    509     public void testPrimaryOnSameVolume() throws Exception {
    510         final File current = getContext().getFilesDir();
    511         final File primary = Environment.getExternalStorageDirectory();
    512 
    513         // Shared storage may jump through another filesystem for permission
    514         // enforcement, so we verify that total/free space are identical.
    515         final long totalDelta = Math.abs(current.getTotalSpace() - primary.getTotalSpace());
    516         final long freeDelta = Math.abs(current.getFreeSpace() - primary.getFreeSpace());
    517         if (totalDelta > MB_IN_BYTES || freeDelta > MB_IN_BYTES) {
    518             fail("Expected primary storage to be on same volume as app");
    519         }
    520     }
    521 
    522     public void testCodeCacheWrite() throws Exception {
    523         assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
    524         assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
    525     }
    526 
    527     public void testCodeCacheRead() throws Exception {
    528         assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists());
    529         assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists());
    530     }
    531 
    532     public void testRevision0_0() throws Exception {
    533         final PackageInfo info = getContext().getPackageManager()
    534                 .getPackageInfo(getContext().getPackageName(), 0);
    535         assertEquals(0, info.baseRevisionCode);
    536         assertEquals(1, info.splitRevisionCodes.length);
    537         assertEquals(0, info.splitRevisionCodes[0]);
    538     }
    539 
    540     public void testRevision12_0() throws Exception {
    541         final PackageInfo info = getContext().getPackageManager()
    542                 .getPackageInfo(getContext().getPackageName(), 0);
    543         assertEquals(12, info.baseRevisionCode);
    544         assertEquals(1, info.splitRevisionCodes.length);
    545         assertEquals(0, info.splitRevisionCodes[0]);
    546     }
    547 
    548     public void testRevision0_12() throws Exception {
    549         final PackageInfo info = getContext().getPackageManager()
    550                 .getPackageInfo(getContext().getPackageName(), 0);
    551         assertEquals(0, info.baseRevisionCode);
    552         assertEquals(1, info.splitRevisionCodes.length);
    553         assertEquals(12, info.splitRevisionCodes[0]);
    554     }
    555 
    556     private static void updateDpi(Resources r, int densityDpi) {
    557         final Configuration c = new Configuration(r.getConfiguration());
    558         c.densityDpi = densityDpi;
    559         r.updateConfiguration(c, r.getDisplayMetrics());
    560     }
    561 
    562     private static void updateLocale(Resources r, Locale locale) {
    563         final Configuration c = new Configuration(r.getConfiguration());
    564         c.locale = locale;
    565         r.updateConfiguration(c, r.getDisplayMetrics());
    566     }
    567 
    568     private static int getDrawableColor(Drawable d) {
    569         final Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
    570                 Bitmap.Config.ARGB_8888);
    571         final Canvas canvas = new Canvas(bitmap);
    572         d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
    573         d.draw(canvas);
    574         return bitmap.getPixel(0, 0);
    575     }
    576 
    577     private static String getXmlTestValue(XmlPullParser in) throws XmlPullParserException,
    578             IOException {
    579         int type;
    580         while ((type = in.next()) != END_DOCUMENT) {
    581             if (type == START_TAG) {
    582                 final String tag = in.getName();
    583                 if ("tag".equals(tag)) {
    584                     return in.getAttributeValue(null, "value");
    585                 }
    586             }
    587         }
    588         return null;
    589     }
    590 
    591     private static void assertAssetContents(Resources r, String path, String expected)
    592             throws IOException {
    593         BufferedReader in = null;
    594         try {
    595             in = new BufferedReader(new InputStreamReader(r.getAssets().open(path)));
    596             assertEquals(expected, in.readLine());
    597         } finally {
    598             if (in != null) in.close();
    599         }
    600     }
    601 
    602     private static void writeString(File file, String value) throws IOException {
    603         final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
    604         try {
    605             os.writeUTF(value);
    606         } finally {
    607             os.close();
    608         }
    609     }
    610 
    611     private static String readString(File file) throws IOException {
    612         final DataInputStream is = new DataInputStream(new FileInputStream(file));
    613         try {
    614             return is.readUTF();
    615         } finally {
    616             is.close();
    617         }
    618     }
    619 }
    620