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