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 
testNothing()75     public void testNothing() throws Exception {
76     }
77 
testSingleBase()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 
testDensitySingle()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 
testDensityAll()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 
testDensityBest1()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 
testDensityBest2()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 
testApi()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 
testLocale()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 
testNative()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 
testFeatureBase()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 
createLaunchIntent()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 
testBaseInstalled()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      */
testFeatureInstalled()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 
testFeatureApi()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      */
testDataWrite()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      */
testDataRead()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      */
testDataInternal()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      */
testDataNotInternal()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 
testPrimaryDataWrite()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 
testPrimaryDataRead()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      */
testPrimaryInternal()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      */
testPrimaryPhysical()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      */
testPrimaryAdopted()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      */
testPrimaryUnmounted()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      */
testPrimaryOnSameVolume()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 * 300 || freeDelta > MB_IN_BYTES * 300) {
518             fail("Expected primary storage to be on same volume as app");
519         }
520     }
521 
testCodeCacheWrite()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 
testCodeCacheRead()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 
testRevision0_0()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 
testRevision12_0()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 
testRevision0_12()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 
updateDpi(Resources r, int densityDpi)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 
updateLocale(Resources r, Locale locale)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 
getDrawableColor(Drawable d)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 
getXmlTestValue(XmlPullParser in)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 
assertAssetContents(Resources r, String path, String expected)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 
writeString(File file, String value)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 
readString(File file)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