| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.normalapp; |
| |
| import static org.hamcrest.CoreMatchers.hasItems; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.CoreMatchers.notNullValue; |
| import static org.hamcrest.CoreMatchers.nullValue; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.fail; |
| |
| import android.content.ActivityNotFoundException; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ResolveInfo; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.provider.Settings.Secure; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.cts.util.TestResult; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.List; |
| import java.util.concurrent.SynchronousQueue; |
| import java.util.concurrent.TimeUnit; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class ClientTest { |
| /** Action to start normal test activities */ |
| private static final String ACTION_START_NORMAL_ACTIVITY = |
| "com.android.cts.ephemeraltest.START_NORMAL"; |
| /** Action to start normal, exposed test activities */ |
| private static final String ACTION_START_EXPOSED_ACTIVITY = |
| "com.android.cts.ephemeraltest.START_EXPOSED"; |
| /** Action to start ephemeral test activities */ |
| private static final String ACTION_START_EPHEMERAL_ACTIVITY = |
| "com.android.cts.ephemeraltest.START_EPHEMERAL"; |
| /** Action to query for test activities */ |
| private static final String ACTION_QUERY = |
| "com.android.cts.ephemeraltest.QUERY"; |
| private static final String EXTRA_ACTIVITY_NAME = |
| "com.android.cts.ephemeraltest.EXTRA_ACTIVITY_NAME"; |
| private static final String EXTRA_ACTIVITY_RESULT = |
| "com.android.cts.ephemeraltest.EXTRA_ACTIVITY_RESULT"; |
| |
| private BroadcastReceiver mReceiver; |
| private final SynchronousQueue<TestResult> mResultQueue = new SynchronousQueue<>(); |
| |
| @Before |
| public void setUp() throws Exception { |
| final IntentFilter filter = |
| new IntentFilter("com.android.cts.ephemeraltest.START_ACTIVITY"); |
| filter.addCategory(Intent.CATEGORY_DEFAULT); |
| mReceiver = new ActivityBroadcastReceiver(mResultQueue); |
| InstrumentationRegistry.getContext() |
| .registerReceiver(mReceiver, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| InstrumentationRegistry.getContext().unregisterReceiver(mReceiver); |
| } |
| |
| @Test |
| public void testQuery() throws Exception { |
| // query activities without flags |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/); |
| if (resolveInfo == null || resolveInfo.size() == 0) { |
| fail("didn't resolve any intents"); |
| } |
| assertThat(resolveInfo.size(), is(2)); |
| assertThat(resolveInfo.get(0).activityInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(0).activityInfo.name, |
| is("com.android.cts.normalapp.ExposedActivity")); |
| assertThat(resolveInfo.get(0).isInstantAppAvailable, |
| is(false)); |
| assertThat(resolveInfo.get(1).activityInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(1).activityInfo.name, |
| is("com.android.cts.normalapp.NormalActivity")); |
| assertThat(resolveInfo.get(1).isInstantAppAvailable, |
| is(false)); |
| } |
| |
| // query activities asking for ephemeral apps [we should only get normal apps] |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| final int MATCH_EPHEMERAL = 0x00800000; |
| |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentActivities(queryIntent, MATCH_EPHEMERAL); |
| if (resolveInfo == null || resolveInfo.size() == 0) { |
| fail("didn't resolve any intents"); |
| } |
| assertThat(resolveInfo.size(), is(2)); |
| assertThat(resolveInfo.get(0).activityInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(0).activityInfo.name, |
| is("com.android.cts.normalapp.ExposedActivity")); |
| assertThat(resolveInfo.get(0).isInstantAppAvailable, |
| is(false)); |
| assertThat(resolveInfo.get(1).activityInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(1).activityInfo.name, |
| is("com.android.cts.normalapp.NormalActivity")); |
| assertThat(resolveInfo.get(1).isInstantAppAvailable, |
| is(false)); |
| } |
| |
| // query activities; directed package |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| queryIntent.setPackage("com.android.cts.ephemeralapp1"); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/); |
| assertThat(resolveInfo.size(), is(0)); |
| } |
| |
| // query activities; directed component |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| queryIntent.setComponent( |
| new ComponentName("com.android.cts.ephemeralapp1", |
| "com.android.cts.ephemeralapp1.EphemeralActivity")); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/); |
| assertThat(resolveInfo.size(), is(0)); |
| } |
| |
| // query services without flags |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/); |
| if (resolveInfo == null || resolveInfo.size() == 0) { |
| fail("didn't resolve any intents"); |
| } |
| assertThat(resolveInfo.size(), is(2)); |
| assertThat(resolveInfo.get(0).serviceInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(0).serviceInfo.name, |
| is("com.android.cts.normalapp.ExposedService")); |
| assertThat(resolveInfo.get(0).isInstantAppAvailable, |
| is(false)); |
| assertThat(resolveInfo.get(1).serviceInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(1).serviceInfo.name, |
| is("com.android.cts.normalapp.NormalService")); |
| assertThat(resolveInfo.get(1).isInstantAppAvailable, |
| is(false)); |
| } |
| |
| // query services asking for ephemeral apps [we should only get normal apps] |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| final int MATCH_EPHEMERAL = 0x00800000; |
| |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentServices(queryIntent, MATCH_EPHEMERAL); |
| if (resolveInfo == null || resolveInfo.size() == 0) { |
| fail("didn't resolve any intents"); |
| } |
| assertThat(resolveInfo.size(), is(2)); |
| assertThat(resolveInfo.get(0).serviceInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(0).serviceInfo.name, |
| is("com.android.cts.normalapp.ExposedService")); |
| assertThat(resolveInfo.get(0).isInstantAppAvailable, |
| is(false)); |
| assertThat(resolveInfo.get(1).serviceInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(1).serviceInfo.name, |
| is("com.android.cts.normalapp.NormalService")); |
| assertThat(resolveInfo.get(1).isInstantAppAvailable, |
| is(false)); |
| } |
| |
| // query services; directed package |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| queryIntent.setPackage("com.android.cts.ephemeralapp1"); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/); |
| assertThat(resolveInfo.size(), is(0)); |
| } |
| |
| // query services; directed component |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| queryIntent.setComponent( |
| new ComponentName("com.android.cts.ephemeralapp1", |
| "com.android.cts.ephemeralapp1.EphemeralService")); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/); |
| assertThat(resolveInfo.size(), is(0)); |
| } |
| |
| // query content providers without flags |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry |
| .getContext().getPackageManager().queryIntentContentProviders( |
| queryIntent, 0 /*flags*/); |
| if (resolveInfo == null || resolveInfo.size() == 0) { |
| fail("didn't resolve any intents"); |
| } |
| assertThat(resolveInfo.size(), is(2)); |
| assertThat(resolveInfo.get(0).providerInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(0).providerInfo.name, |
| is("com.android.cts.normalapp.ExposedProvider")); |
| assertThat(resolveInfo.get(1).providerInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(1).providerInfo.name, |
| is("com.android.cts.normalapp.NormalProvider")); |
| assertThat(resolveInfo.get(1).isInstantAppAvailable, |
| is(false)); |
| } |
| |
| // query content providers asking for ephemeral apps [we should only get normal apps] |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| final int MATCH_EPHEMERAL = 0x00800000; |
| |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentContentProviders( |
| queryIntent, MATCH_EPHEMERAL); |
| if (resolveInfo == null || resolveInfo.size() == 0) { |
| fail("didn't resolve any intents"); |
| } |
| assertThat(resolveInfo.size(), is(2)); |
| assertThat(resolveInfo.get(0).providerInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(0).providerInfo.name, |
| is("com.android.cts.normalapp.ExposedProvider")); |
| assertThat(resolveInfo.get(1).providerInfo.packageName, |
| is("com.android.cts.normalapp")); |
| assertThat(resolveInfo.get(1).providerInfo.name, |
| is("com.android.cts.normalapp.NormalProvider")); |
| assertThat(resolveInfo.get(1).isInstantAppAvailable, |
| is(false)); |
| } |
| |
| // query content providers; directed package |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| queryIntent.setPackage("com.android.cts.ephemeralapp1"); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/); |
| assertThat(resolveInfo.size(), is(0)); |
| } |
| |
| // query content providers; directed component |
| { |
| final Intent queryIntent = new Intent(ACTION_QUERY); |
| queryIntent.setComponent( |
| new ComponentName("com.android.cts.ephemeralapp1", |
| "com.android.cts.ephemeralapp1.EphemeralProvider")); |
| final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext() |
| .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/); |
| assertThat(resolveInfo.size(), is(0)); |
| } |
| } |
| |
| @Test |
| public void testStartNormal() throws Exception { |
| // start the normal activity |
| { |
| final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| assertThat(testResult.getPackageName(), |
| is("com.android.cts.normalapp")); |
| assertThat(testResult.getComponentName(), |
| is("NormalActivity")); |
| assertThat(testResult.getException(), |
| is("android.content.pm.PackageManager$NameNotFoundException")); |
| } |
| |
| // start the normal activity; directed package |
| { |
| final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startNormalIntent.setPackage("com.android.cts.normalapp"); |
| InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| assertThat(testResult.getPackageName(), |
| is("com.android.cts.normalapp")); |
| assertThat(testResult.getComponentName(), |
| is("NormalActivity")); |
| assertThat(testResult.getException(), |
| is("android.content.pm.PackageManager$NameNotFoundException")); |
| } |
| |
| // start the normal activity; directed component |
| { |
| final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startNormalIntent.setComponent(new ComponentName( |
| "com.android.cts.normalapp", "com.android.cts.normalapp.NormalActivity")); |
| InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| assertThat(testResult.getPackageName(), |
| is("com.android.cts.normalapp")); |
| assertThat(testResult.getComponentName(), |
| is("NormalActivity")); |
| assertThat(testResult.getException(), |
| is("android.content.pm.PackageManager$NameNotFoundException")); |
| } |
| |
| // connect to the normal provider |
| { |
| final String provider = "content://com.android.cts.normalapp.provider/table"; |
| final Cursor testCursor = InstrumentationRegistry |
| .getContext().getContentResolver().query( |
| Uri.parse(provider), |
| null /*projection*/, |
| null /*selection*/, |
| null /*selectionArgs*/, |
| null /*sortOrder*/); |
| assertThat(testCursor, is(notNullValue())); |
| assertThat(testCursor.getCount(), is(1)); |
| assertThat(testCursor.getColumnCount(), is(2)); |
| assertThat(testCursor.moveToFirst(), is(true)); |
| assertThat(testCursor.getInt(0), is(1)); |
| assertThat(testCursor.getString(1), is("NormalProvider")); |
| } |
| } |
| |
| @Test |
| public void testStartEphemeral() throws Exception { |
| // start the ephemeral activity; no EXTERNAL flag |
| try { |
| final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| InstrumentationRegistry.getContext().startActivity( |
| startEphemeralIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| fail(); |
| } catch (ActivityNotFoundException expected) { |
| } |
| |
| // start the ephemeral activity; EXTERNAL flag |
| { |
| final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL); |
| InstrumentationRegistry.getContext().startActivity( |
| startEphemeralIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName())); |
| assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction())); |
| } |
| |
| |
| // start the ephemeral activity; directed package, no EXTERNAL flag |
| try { |
| final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1"); |
| InstrumentationRegistry.getContext().startActivity( |
| startEphemeralIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| fail(); |
| } catch (ActivityNotFoundException expected) { |
| } |
| |
| // start the ephemeral activity; directed package, includes EXTERNAL flag |
| { |
| final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL); |
| startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1"); |
| InstrumentationRegistry.getContext().startActivity( |
| startEphemeralIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName())); |
| assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction())); |
| } |
| |
| // start the ephemeral activity; directed component |
| try { |
| final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startEphemeralIntent.setComponent( |
| new ComponentName("com.android.cts.ephemeralapp1", |
| "com.android.cts.ephemeralapp1.EphemeralActivity")); |
| InstrumentationRegistry.getContext().startActivity( |
| startEphemeralIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| fail(); |
| } catch (ActivityNotFoundException expected) { |
| } |
| |
| // start the ephemeral activity; using VIEW/BROWSABLE |
| { |
| final Intent startViewIntent = new Intent(Intent.ACTION_VIEW) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE); |
| startViewIntent.setData(Uri.parse("https://cts.google.com/ephemeral")); |
| InstrumentationRegistry.getContext().startActivity( |
| startViewIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName())); |
| assertThat("EphemeralActivity", is(testResult.getComponentName())); |
| assertThat(Intent.ACTION_VIEW, is(testResult.getIntent().getAction())); |
| assertThat(testResult.getIntent().getCategories(), hasItems(Intent.CATEGORY_BROWSABLE)); |
| assertThat("https://cts.google.com/ephemeral", |
| is(testResult.getIntent().getData().toString())); |
| } |
| |
| final ContentResolver contentResolver = |
| InstrumentationRegistry.getContext().getContentResolver(); |
| final int originalSetting = Secure.getInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 1); |
| Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 0); |
| try { |
| Thread.sleep(1000); |
| } catch (Exception e) { |
| } |
| try { |
| // start the ephemeral activity; using VIEW/BROWSABLE with setting disabled |
| try { |
| final Intent startViewIntent = new Intent(Intent.ACTION_VIEW) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE); |
| startViewIntent.setData(Uri.parse("https://cts.google.com/ephemeral")); |
| InstrumentationRegistry.getContext().startActivity( |
| startViewIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| fail(); |
| } catch (TestResultNotFoundException expected) { |
| // we shouldn't resolve to our instant app |
| } |
| |
| // start the ephemeral activity; EXTERNAL flag with setting disabled |
| { |
| final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL); |
| InstrumentationRegistry.getContext().startActivity( |
| startEphemeralIntent, null /*options*/); |
| final TestResult testResult = getResult(); |
| assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName())); |
| assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction())); |
| } |
| |
| } finally { |
| Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, originalSetting); |
| } |
| |
| // connect to the instant app provider |
| { |
| final String provider = "content://com.android.cts.ephemeralapp1.provider/table"; |
| final Cursor testCursor = contentResolver.query( |
| Uri.parse(provider), |
| null /*projection*/, |
| null /*selection*/, |
| null /*selectionArgs*/, |
| null /*sortOrder*/); |
| assertThat(testCursor, is(nullValue())); |
| } |
| } |
| |
| private TestResult getResult() { |
| final TestResult result; |
| try { |
| result = mResultQueue.poll(5, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| if (result == null) { |
| throw new TestResultNotFoundException( |
| "Activity didn't receive a Result in 5 seconds"); |
| } |
| return result; |
| } |
| |
| private static class TestResultNotFoundException extends IllegalStateException { |
| public TestResultNotFoundException(String description) { |
| super(description); |
| } |
| } |
| |
| private static class ActivityBroadcastReceiver extends BroadcastReceiver { |
| private final SynchronousQueue<TestResult> mQueue; |
| public ActivityBroadcastReceiver(SynchronousQueue<TestResult> queue) { |
| mQueue = queue; |
| } |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| try { |
| mQueue.offer(intent.getParcelableExtra(TestResult.EXTRA_TEST_RESULT), |
| 5, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| } |