Home | History | Annotate | Download | only in normalapp
      1 /*
      2  * Copyright (C) 2016 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.normalapp;
     18 
     19 import static org.hamcrest.CoreMatchers.hasItems;
     20 import static org.hamcrest.CoreMatchers.is;
     21 import static org.hamcrest.CoreMatchers.notNullValue;
     22 import static org.hamcrest.CoreMatchers.nullValue;
     23 import static org.junit.Assert.assertThat;
     24 import static org.junit.Assert.fail;
     25 
     26 import android.content.ActivityNotFoundException;
     27 import android.content.BroadcastReceiver;
     28 import android.content.ComponentName;
     29 import android.content.ContentResolver;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.content.IntentFilter;
     33 import android.content.pm.ResolveInfo;
     34 import android.database.Cursor;
     35 import android.net.Uri;
     36 import android.provider.Settings.Secure;
     37 import android.support.test.InstrumentationRegistry;
     38 import android.support.test.runner.AndroidJUnit4;
     39 
     40 import com.android.cts.util.TestResult;
     41 
     42 import org.junit.After;
     43 import org.junit.Before;
     44 import org.junit.Test;
     45 import org.junit.runner.RunWith;
     46 
     47 import java.util.List;
     48 import java.util.concurrent.SynchronousQueue;
     49 import java.util.concurrent.TimeUnit;
     50 
     51 @RunWith(AndroidJUnit4.class)
     52 public class ClientTest {
     53     /** Action to start normal test activities */
     54     private static final String ACTION_START_NORMAL_ACTIVITY =
     55             "com.android.cts.ephemeraltest.START_NORMAL";
     56     /** Action to start normal, exposed test activities */
     57     private static final String ACTION_START_EXPOSED_ACTIVITY =
     58             "com.android.cts.ephemeraltest.START_EXPOSED";
     59     /** Action to start ephemeral test activities */
     60     private static final String ACTION_START_EPHEMERAL_ACTIVITY =
     61             "com.android.cts.ephemeraltest.START_EPHEMERAL";
     62     /** Action to query for test activities */
     63     private static final String ACTION_QUERY =
     64             "com.android.cts.ephemeraltest.QUERY";
     65     private static final String EXTRA_ACTIVITY_NAME =
     66             "com.android.cts.ephemeraltest.EXTRA_ACTIVITY_NAME";
     67     private static final String EXTRA_ACTIVITY_RESULT =
     68             "com.android.cts.ephemeraltest.EXTRA_ACTIVITY_RESULT";
     69 
     70     private BroadcastReceiver mReceiver;
     71     private final SynchronousQueue<TestResult> mResultQueue = new SynchronousQueue<>();
     72 
     73     @Before
     74     public void setUp() throws Exception {
     75         final IntentFilter filter =
     76                 new IntentFilter("com.android.cts.ephemeraltest.START_ACTIVITY");
     77         filter.addCategory(Intent.CATEGORY_DEFAULT);
     78         mReceiver = new ActivityBroadcastReceiver(mResultQueue);
     79         InstrumentationRegistry.getContext()
     80                 .registerReceiver(mReceiver, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
     81     }
     82 
     83     @After
     84     public void tearDown() throws Exception {
     85         InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
     86     }
     87 
     88     @Test
     89     public void testQuery() throws Exception {
     90         // query activities without flags
     91         {
     92             final Intent queryIntent = new Intent(ACTION_QUERY);
     93             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
     94                     .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/);
     95             if (resolveInfo == null || resolveInfo.size() == 0) {
     96                 fail("didn't resolve any intents");
     97             }
     98             assertThat(resolveInfo.size(), is(2));
     99             assertThat(resolveInfo.get(0).activityInfo.packageName,
    100                     is("com.android.cts.normalapp"));
    101             assertThat(resolveInfo.get(0).activityInfo.name,
    102                     is("com.android.cts.normalapp.ExposedActivity"));
    103             assertThat(resolveInfo.get(0).isInstantAppAvailable,
    104                     is(false));
    105             assertThat(resolveInfo.get(1).activityInfo.packageName,
    106                     is("com.android.cts.normalapp"));
    107             assertThat(resolveInfo.get(1).activityInfo.name,
    108                     is("com.android.cts.normalapp.NormalActivity"));
    109             assertThat(resolveInfo.get(1).isInstantAppAvailable,
    110                     is(false));
    111         }
    112 
    113         // query activities asking for ephemeral apps [we should only get normal apps]
    114         {
    115             final Intent queryIntent = new Intent(ACTION_QUERY);
    116             final int MATCH_EPHEMERAL = 0x00800000;
    117 
    118             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    119                     .getPackageManager().queryIntentActivities(queryIntent, MATCH_EPHEMERAL);
    120             if (resolveInfo == null || resolveInfo.size() == 0) {
    121                 fail("didn't resolve any intents");
    122             }
    123             assertThat(resolveInfo.size(), is(2));
    124             assertThat(resolveInfo.get(0).activityInfo.packageName,
    125                     is("com.android.cts.normalapp"));
    126             assertThat(resolveInfo.get(0).activityInfo.name,
    127                     is("com.android.cts.normalapp.ExposedActivity"));
    128             assertThat(resolveInfo.get(0).isInstantAppAvailable,
    129                     is(false));
    130             assertThat(resolveInfo.get(1).activityInfo.packageName,
    131                     is("com.android.cts.normalapp"));
    132             assertThat(resolveInfo.get(1).activityInfo.name,
    133                     is("com.android.cts.normalapp.NormalActivity"));
    134             assertThat(resolveInfo.get(1).isInstantAppAvailable,
    135                     is(false));
    136         }
    137 
    138         // query activities; directed package
    139         {
    140             final Intent queryIntent = new Intent(ACTION_QUERY);
    141             queryIntent.setPackage("com.android.cts.ephemeralapp1");
    142             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    143                     .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/);
    144             assertThat(resolveInfo.size(), is(0));
    145         }
    146 
    147         // query activities; directed component
    148         {
    149             final Intent queryIntent = new Intent(ACTION_QUERY);
    150             queryIntent.setComponent(
    151                     new ComponentName("com.android.cts.ephemeralapp1",
    152                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
    153             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    154                     .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/);
    155             assertThat(resolveInfo.size(), is(0));
    156         }
    157 
    158         // query services without flags
    159         {
    160             final Intent queryIntent = new Intent(ACTION_QUERY);
    161             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    162                     .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/);
    163             if (resolveInfo == null || resolveInfo.size() == 0) {
    164                 fail("didn't resolve any intents");
    165             }
    166             assertThat(resolveInfo.size(), is(2));
    167             assertThat(resolveInfo.get(0).serviceInfo.packageName,
    168                     is("com.android.cts.normalapp"));
    169             assertThat(resolveInfo.get(0).serviceInfo.name,
    170                     is("com.android.cts.normalapp.ExposedService"));
    171             assertThat(resolveInfo.get(0).isInstantAppAvailable,
    172                     is(false));
    173             assertThat(resolveInfo.get(1).serviceInfo.packageName,
    174                     is("com.android.cts.normalapp"));
    175             assertThat(resolveInfo.get(1).serviceInfo.name,
    176                     is("com.android.cts.normalapp.NormalService"));
    177             assertThat(resolveInfo.get(1).isInstantAppAvailable,
    178                     is(false));
    179         }
    180 
    181         // query services asking for ephemeral apps [we should only get normal apps]
    182         {
    183             final Intent queryIntent = new Intent(ACTION_QUERY);
    184             final int MATCH_EPHEMERAL = 0x00800000;
    185 
    186             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    187                     .getPackageManager().queryIntentServices(queryIntent, MATCH_EPHEMERAL);
    188             if (resolveInfo == null || resolveInfo.size() == 0) {
    189                 fail("didn't resolve any intents");
    190             }
    191             assertThat(resolveInfo.size(), is(2));
    192             assertThat(resolveInfo.get(0).serviceInfo.packageName,
    193                     is("com.android.cts.normalapp"));
    194             assertThat(resolveInfo.get(0).serviceInfo.name,
    195                     is("com.android.cts.normalapp.ExposedService"));
    196             assertThat(resolveInfo.get(0).isInstantAppAvailable,
    197                     is(false));
    198             assertThat(resolveInfo.get(1).serviceInfo.packageName,
    199                     is("com.android.cts.normalapp"));
    200             assertThat(resolveInfo.get(1).serviceInfo.name,
    201                     is("com.android.cts.normalapp.NormalService"));
    202             assertThat(resolveInfo.get(1).isInstantAppAvailable,
    203                     is(false));
    204         }
    205 
    206         // query services; directed package
    207         {
    208             final Intent queryIntent = new Intent(ACTION_QUERY);
    209             queryIntent.setPackage("com.android.cts.ephemeralapp1");
    210             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    211                     .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/);
    212             assertThat(resolveInfo.size(), is(0));
    213         }
    214 
    215         // query services; directed component
    216         {
    217             final Intent queryIntent = new Intent(ACTION_QUERY);
    218             queryIntent.setComponent(
    219                     new ComponentName("com.android.cts.ephemeralapp1",
    220                             "com.android.cts.ephemeralapp1.EphemeralService"));
    221             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    222                     .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/);
    223             assertThat(resolveInfo.size(), is(0));
    224         }
    225 
    226         // query content providers without flags
    227         {
    228             final Intent queryIntent = new Intent(ACTION_QUERY);
    229             final List<ResolveInfo> resolveInfo = InstrumentationRegistry
    230                     .getContext().getPackageManager().queryIntentContentProviders(
    231                             queryIntent, 0 /*flags*/);
    232             if (resolveInfo == null || resolveInfo.size() == 0) {
    233                 fail("didn't resolve any intents");
    234             }
    235             assertThat(resolveInfo.size(), is(2));
    236             assertThat(resolveInfo.get(0).providerInfo.packageName,
    237                     is("com.android.cts.normalapp"));
    238             assertThat(resolveInfo.get(0).providerInfo.name,
    239                     is("com.android.cts.normalapp.ExposedProvider"));
    240             assertThat(resolveInfo.get(1).providerInfo.packageName,
    241                     is("com.android.cts.normalapp"));
    242             assertThat(resolveInfo.get(1).providerInfo.name,
    243                     is("com.android.cts.normalapp.NormalProvider"));
    244             assertThat(resolveInfo.get(1).isInstantAppAvailable,
    245                     is(false));
    246         }
    247 
    248         // query content providers asking for ephemeral apps [we should only get normal apps]
    249         {
    250             final Intent queryIntent = new Intent(ACTION_QUERY);
    251             final int MATCH_EPHEMERAL = 0x00800000;
    252 
    253             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    254                     .getPackageManager().queryIntentContentProviders(
    255                             queryIntent, MATCH_EPHEMERAL);
    256             if (resolveInfo == null || resolveInfo.size() == 0) {
    257                 fail("didn't resolve any intents");
    258             }
    259             assertThat(resolveInfo.size(), is(2));
    260             assertThat(resolveInfo.get(0).providerInfo.packageName,
    261                     is("com.android.cts.normalapp"));
    262             assertThat(resolveInfo.get(0).providerInfo.name,
    263                     is("com.android.cts.normalapp.ExposedProvider"));
    264             assertThat(resolveInfo.get(1).providerInfo.packageName,
    265                     is("com.android.cts.normalapp"));
    266             assertThat(resolveInfo.get(1).providerInfo.name,
    267                     is("com.android.cts.normalapp.NormalProvider"));
    268             assertThat(resolveInfo.get(1).isInstantAppAvailable,
    269                     is(false));
    270         }
    271 
    272         // query content providers; directed package
    273         {
    274             final Intent queryIntent = new Intent(ACTION_QUERY);
    275             queryIntent.setPackage("com.android.cts.ephemeralapp1");
    276             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    277                     .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/);
    278             assertThat(resolveInfo.size(), is(0));
    279         }
    280 
    281         // query content providers; directed component
    282         {
    283             final Intent queryIntent = new Intent(ACTION_QUERY);
    284             queryIntent.setComponent(
    285                     new ComponentName("com.android.cts.ephemeralapp1",
    286                             "com.android.cts.ephemeralapp1.EphemeralProvider"));
    287             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
    288                     .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/);
    289             assertThat(resolveInfo.size(), is(0));
    290         }
    291     }
    292 
    293     @Test
    294     public void testStartNormal() throws Exception {
    295         // start the normal activity
    296         {
    297             final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY)
    298                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    299             InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/);
    300             final TestResult testResult = getResult();
    301             assertThat(testResult.getPackageName(),
    302                     is("com.android.cts.normalapp"));
    303             assertThat(testResult.getComponentName(),
    304                     is("NormalActivity"));
    305             assertThat(testResult.getException(),
    306                     is("android.content.pm.PackageManager$NameNotFoundException"));
    307         }
    308 
    309         // start the normal activity; directed package
    310         {
    311             final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY)
    312                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    313             startNormalIntent.setPackage("com.android.cts.normalapp");
    314             InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/);
    315             final TestResult testResult = getResult();
    316             assertThat(testResult.getPackageName(),
    317                     is("com.android.cts.normalapp"));
    318             assertThat(testResult.getComponentName(),
    319                     is("NormalActivity"));
    320             assertThat(testResult.getException(),
    321                     is("android.content.pm.PackageManager$NameNotFoundException"));
    322         }
    323 
    324         // start the normal activity; directed component
    325         {
    326             final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY)
    327                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    328             startNormalIntent.setComponent(new ComponentName(
    329                     "com.android.cts.normalapp", "com.android.cts.normalapp.NormalActivity"));
    330             InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/);
    331             final TestResult testResult = getResult();
    332             assertThat(testResult.getPackageName(),
    333                     is("com.android.cts.normalapp"));
    334             assertThat(testResult.getComponentName(),
    335                     is("NormalActivity"));
    336             assertThat(testResult.getException(),
    337                     is("android.content.pm.PackageManager$NameNotFoundException"));
    338         }
    339 
    340         // connect to the normal provider
    341         {
    342             final String provider = "content://com.android.cts.normalapp.provider/table";
    343             final Cursor testCursor = InstrumentationRegistry
    344                     .getContext().getContentResolver().query(
    345                             Uri.parse(provider),
    346                             null /*projection*/,
    347                             null /*selection*/,
    348                             null /*selectionArgs*/,
    349                             null /*sortOrder*/);
    350             assertThat(testCursor, is(notNullValue()));
    351             assertThat(testCursor.getCount(), is(1));
    352             assertThat(testCursor.getColumnCount(), is(2));
    353             assertThat(testCursor.moveToFirst(), is(true));
    354             assertThat(testCursor.getInt(0), is(1));
    355             assertThat(testCursor.getString(1), is("NormalProvider"));
    356         }
    357     }
    358 
    359     @Test
    360     public void testStartEphemeral() throws Exception {
    361         // start the ephemeral activity; no EXTERNAL flag
    362         try {
    363             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
    364                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    365             InstrumentationRegistry.getContext().startActivity(
    366                     startEphemeralIntent, null /*options*/);
    367             final TestResult testResult = getResult();
    368             fail();
    369         } catch (ActivityNotFoundException expected) {
    370         }
    371 
    372         // start the ephemeral activity; EXTERNAL flag
    373         {
    374             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
    375                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
    376             InstrumentationRegistry.getContext().startActivity(
    377                     startEphemeralIntent, null /*options*/);
    378             final TestResult testResult = getResult();
    379             assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
    380             assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
    381         }
    382 
    383 
    384         // start the ephemeral activity; directed package, no EXTERNAL flag
    385         try {
    386             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
    387                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    388             startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
    389             InstrumentationRegistry.getContext().startActivity(
    390                     startEphemeralIntent, null /*options*/);
    391             final TestResult testResult = getResult();
    392             fail();
    393         } catch (ActivityNotFoundException expected) {
    394         }
    395 
    396         // start the ephemeral activity; directed package, includes EXTERNAL flag
    397         {
    398             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
    399                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
    400             startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
    401             InstrumentationRegistry.getContext().startActivity(
    402                     startEphemeralIntent, null /*options*/);
    403             final TestResult testResult = getResult();
    404             assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
    405             assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
    406         }
    407 
    408         // start the ephemeral activity; directed component
    409         try {
    410             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
    411                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    412             startEphemeralIntent.setComponent(
    413                     new ComponentName("com.android.cts.ephemeralapp1",
    414                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
    415             InstrumentationRegistry.getContext().startActivity(
    416                     startEphemeralIntent, null /*options*/);
    417             final TestResult testResult = getResult();
    418             fail();
    419         } catch (ActivityNotFoundException expected) {
    420         }
    421 
    422         // start the ephemeral activity; using VIEW/BROWSABLE
    423         {
    424             final Intent startViewIntent = new Intent(Intent.ACTION_VIEW)
    425                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    426             startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE);
    427             startViewIntent.setData(Uri.parse("https://cts.google.com/ephemeral"));
    428             InstrumentationRegistry.getContext().startActivity(
    429                     startViewIntent, null /*options*/);
    430             final TestResult testResult = getResult();
    431             assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
    432             assertThat("EphemeralActivity", is(testResult.getComponentName()));
    433             assertThat(Intent.ACTION_VIEW, is(testResult.getIntent().getAction()));
    434             assertThat(testResult.getIntent().getCategories(), hasItems(Intent.CATEGORY_BROWSABLE));
    435             assertThat("https://cts.google.com/ephemeral",
    436                     is(testResult.getIntent().getData().toString()));
    437         }
    438 
    439         final ContentResolver contentResolver =
    440                 InstrumentationRegistry.getContext().getContentResolver();
    441         final int originalSetting = Secure.getInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 1);
    442         Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 0);
    443         try {
    444             // start the ephemeral activity; using VIEW/BROWSABLE with setting disabled
    445             try {
    446                 final Intent startViewIntent = new Intent(Intent.ACTION_VIEW)
    447                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    448                 startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE);
    449                 startViewIntent.setData(Uri.parse("https://cts.google.com/ephemeral"));
    450                 InstrumentationRegistry.getContext().startActivity(
    451                         startViewIntent, null /*options*/);
    452                 final TestResult testResult = getResult();
    453                 fail();
    454             } catch (TestResultNotFoundException expected) {
    455                 // we shouldn't resolve to our instant app
    456             }
    457 
    458             // start the ephemeral activity; EXTERNAL flag with setting disabled
    459             {
    460                 final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
    461                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    462                                 | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
    463                 InstrumentationRegistry.getContext().startActivity(
    464                         startEphemeralIntent, null /*options*/);
    465                 final TestResult testResult = getResult();
    466                 assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
    467                 assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
    468             }
    469 
    470         } finally {
    471             Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, originalSetting);
    472         }
    473 
    474         // connect to the instant app provider
    475         {
    476             final String provider = "content://com.android.cts.ephemeralapp1.provider/table";
    477             final Cursor testCursor = contentResolver.query(
    478                             Uri.parse(provider),
    479                             null /*projection*/,
    480                             null /*selection*/,
    481                             null /*selectionArgs*/,
    482                             null /*sortOrder*/);
    483             assertThat(testCursor, is(nullValue()));
    484         }
    485     }
    486 
    487     private TestResult getResult() {
    488         final TestResult result;
    489         try {
    490             result = mResultQueue.poll(5, TimeUnit.SECONDS);
    491         } catch (InterruptedException e) {
    492             throw new RuntimeException(e);
    493         }
    494         if (result == null) {
    495             throw new TestResultNotFoundException(
    496                     "Activity didn't receive a Result in 5 seconds");
    497         }
    498         return result;
    499     }
    500 
    501     private static class TestResultNotFoundException extends IllegalStateException {
    502         public TestResultNotFoundException(String description) {
    503             super(description);
    504         }
    505     }
    506 
    507     private static class ActivityBroadcastReceiver extends BroadcastReceiver {
    508         private final SynchronousQueue<TestResult> mQueue;
    509         public ActivityBroadcastReceiver(SynchronousQueue<TestResult> queue) {
    510             mQueue = queue;
    511         }
    512 
    513         @Override
    514         public void onReceive(Context context, Intent intent) {
    515             try {
    516                 mQueue.offer(intent.getParcelableExtra(TestResult.EXTRA_TEST_RESULT),
    517                         5, TimeUnit.SECONDS);
    518             } catch (InterruptedException e) {
    519                 throw new RuntimeException(e);
    520             }
    521         }
    522     }
    523 }
    524