| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.content.browser.installedapp; |
| |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.AssetManager; |
| import android.content.res.Resources; |
| import android.os.Bundle; |
| |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import org.robolectric.RuntimeEnvironment; |
| import org.robolectric.annotation.Config; |
| import org.robolectric.res.builder.DefaultPackageManager; |
| |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.installedapp.mojom.InstalledAppProvider; |
| import org.chromium.installedapp.mojom.RelatedApplication; |
| import org.chromium.testing.local.CustomShadowAsyncTask; |
| import org.chromium.testing.local.LocalRobolectricTestRunner; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.HashMap; |
| |
| /** |
| * Ensure that the InstalledAppProvider returns the correct apps. |
| */ |
| @RunWith(LocalRobolectricTestRunner.class) |
| @Config(manifest = Config.NONE, shadows = {CustomShadowAsyncTask.class}) |
| public class InstalledAppProviderTest { |
| private static final String ASSET_STATEMENTS_KEY = |
| InstalledAppProviderImpl.ASSET_STATEMENTS_KEY; |
| private static final String RELATION_HANDLE_ALL_URLS = |
| "delegate_permission/common.handle_all_urls"; |
| private static final String NAMESPACE_WEB = |
| InstalledAppProviderImpl.ASSET_STATEMENT_NAMESPACE_WEB; |
| private static final String PLATFORM_ANDROID = |
| InstalledAppProviderImpl.RELATED_APP_PLATFORM_ANDROID; |
| private static final String PLATFORM_OTHER = "itunes"; |
| // Note: Android package name and origin deliberately unrelated (there is no requirement that |
| // they be the same). |
| private static final String PACKAGE_NAME_1 = "com.app1.package"; |
| private static final String PACKAGE_NAME_2 = "com.app2.package"; |
| private static final String PACKAGE_NAME_3 = "com.app3.package"; |
| private static final String URL_UNRELATED = "https://appstore.example.com/app1"; |
| private static final String ORIGIN = "https://example.com:8000"; |
| private static final String URL_ON_ORIGIN = |
| "https://example.com:8000/path/to/page.html?key=value#fragment"; |
| private static final String ORIGIN_SYNTAX_ERROR = "https:{"; |
| private static final String ORIGIN_MISSING_SCHEME = "path/only"; |
| private static final String ORIGIN_MISSING_HOST = "file:///path/piece"; |
| private static final String ORIGIN_MISSING_PORT = "http://example.com"; |
| private static final String ORIGIN_DIFFERENT_SCHEME = "http://example.com:8000"; |
| private static final String ORIGIN_DIFFERENT_HOST = "https://example.org:8000"; |
| private static final String ORIGIN_DIFFERENT_PORT = "https://example.com:8001"; |
| |
| private FakePackageManager mPackageManager; |
| private FakeFrameUrlDelegate mFrameUrlDelegate; |
| private InstalledAppProviderTestImpl mInstalledAppProvider; |
| |
| private static class InstalledAppProviderTestImpl extends InstalledAppProviderImpl { |
| private long mLastDelayMillis; |
| |
| public InstalledAppProviderTestImpl(FrameUrlDelegate frameUrlDelegate, Context context) { |
| super(frameUrlDelegate, context); |
| } |
| |
| public long getLastDelayMillis() { |
| return mLastDelayMillis; |
| } |
| |
| @Override |
| protected void delayThenRun(Runnable r, long delayMillis) { |
| mLastDelayMillis = delayMillis; |
| r.run(); |
| } |
| } |
| |
| /** |
| * FakePackageManager allows for the "installation" of Android package names and setting up |
| * Resources for installed packages. |
| */ |
| private static class FakePackageManager extends DefaultPackageManager { |
| private final HashMap<String, Bundle> mMetaDataMap; |
| private final HashMap<String, Resources> mResourceMap; |
| |
| public FakePackageManager() { |
| super(); |
| mMetaDataMap = new HashMap<String, Bundle>(); |
| mResourceMap = new HashMap<String, Resources>(); |
| } |
| |
| @Override |
| public ApplicationInfo getApplicationInfo(String packageName, int flags) |
| throws NameNotFoundException { |
| if (packageName == null) throw new NullPointerException(); |
| |
| Bundle metaData = mMetaDataMap.get(packageName); |
| if (metaData == null) throw new NameNotFoundException(packageName); |
| |
| // Create an application with this metadata (but only if |flags| allows). Doing it this |
| // way (rather than simply storing the ApplicationInfo in a map) ensures that the |
| // |flags| is set correctly. |
| ApplicationInfo appInfo = new ApplicationInfo(); |
| appInfo.packageName = packageName; |
| if ((flags & PackageManager.GET_META_DATA) != 0) { |
| appInfo.metaData = metaData; |
| } |
| return appInfo; |
| } |
| |
| @Override |
| public Resources getResourcesForApplication(ApplicationInfo app) |
| throws NameNotFoundException { |
| if (app == null) throw new NullPointerException(); |
| |
| Resources result = mResourceMap.get(app.packageName); |
| if (result == null) throw new NameNotFoundException(app.packageName); |
| |
| return result; |
| } |
| |
| public void setMetaDataAndResourcesForTest( |
| String packageName, Bundle metaData, Resources resources) { |
| mMetaDataMap.put(packageName, metaData); |
| mResourceMap.put(packageName, resources); |
| } |
| } |
| |
| /** |
| * Fakes the Resources object, allowing lookup of a single String value. |
| * |
| * Note: The real Resources object defines a mapping to many values. This fake object only |
| * allows a single value in the mapping, and it must be a String (which is all that is required |
| * for these tests). |
| */ |
| private static class FakeResources extends Resources { |
| private final int mId; |
| private final String mValue; |
| |
| // Do not warn about deprecated call to Resources(); the documentation says code is not |
| // supposed to create its own Resources object, but we are using it to fake out the |
| // Resources, and there is no other way to do that. |
| @SuppressWarnings("deprecation") |
| public FakeResources(int identifier, String value) { |
| super(new AssetManager(), null, null); |
| mId = identifier; |
| mValue = value; |
| } |
| |
| @Override |
| public int getIdentifier(String name, String defType, String defPackage) { |
| if (name == null) throw new NullPointerException(); |
| |
| // There is *no guarantee* (in the Digital Asset Links spec) about what the string |
| // resource should be called ("asset_statements" is just an example). Therefore, |
| // getIdentifier cannot be used to get the asset statements string. Always fail the |
| // lookup here, to ensure the implementation isn't relying on any particular hard-coded |
| // string. |
| return 0; |
| } |
| |
| @Override |
| public String getString(int id) { |
| if (id != mId) { |
| throw new Resources.NotFoundException("id 0x" + Integer.toHexString(id)); |
| } |
| |
| return mValue; |
| } |
| } |
| |
| private static final class FakeFrameUrlDelegate |
| implements InstalledAppProviderImpl.FrameUrlDelegate { |
| private URI mFrameUrl; |
| private boolean mIncognito; |
| |
| public FakeFrameUrlDelegate(String frameUrl) { |
| setFrameUrl(frameUrl); |
| } |
| |
| public void setFrameUrl(String frameUrl) { |
| if (frameUrl == null) { |
| mFrameUrl = null; |
| return; |
| } |
| |
| try { |
| mFrameUrl = new URI(frameUrl); |
| } catch (URISyntaxException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| @Override |
| public URI getUrl() { |
| return mFrameUrl; |
| } |
| |
| public void setIncognito(boolean incognito) { |
| mIncognito = incognito; |
| } |
| |
| @Override |
| public boolean isIncognito() { |
| return mIncognito; |
| } |
| } |
| |
| /** |
| * Creates a metaData bundle with a single resource-id key. |
| */ |
| private static Bundle createMetaData(String metaDataName, int metaDataResourceId) { |
| Bundle metaData = new Bundle(); |
| metaData.putInt(metaDataName, metaDataResourceId); |
| return metaData; |
| } |
| |
| /** |
| * Sets a resource with a single key-value pair in an Android package's manifest. |
| * |
| * The value is always a string. |
| */ |
| private void setStringResource(String packageName, String key, String value) { |
| int identifier = 0x1234; |
| Bundle metaData = createMetaData(key, identifier); |
| FakeResources resources = new FakeResources(identifier, value); |
| mPackageManager.setMetaDataAndResourcesForTest(packageName, metaData, resources); |
| } |
| |
| /** |
| * Creates a valid Android asset statement string. |
| */ |
| private String createAssetStatement(String platform, String relation, String url) { |
| return String.format( |
| "{\"relation\": [\"%s\"], \"target\": {\"namespace\": \"%s\", \"site\": \"%s\"}}", |
| relation, platform, url); |
| } |
| |
| /** |
| * Sets an asset statement to an Android package's manifest (in the fake package manager). |
| * |
| * Only one asset statement can be set for a given package (if this is called twice on the same |
| * package, overwrites the previous asset statement). |
| * |
| * This corresponds to a Statement List in the Digital Asset Links spec v1. |
| */ |
| private void setAssetStatement( |
| String packageName, String platform, String relation, String url) { |
| String statements = "[" + createAssetStatement(platform, relation, url) + "]"; |
| setStringResource(packageName, ASSET_STATEMENTS_KEY, statements); |
| } |
| |
| /** |
| * Creates a RelatedApplication to put in the web app manifest. |
| */ |
| private RelatedApplication createRelatedApplication(String platform, String id, String url) { |
| RelatedApplication application = new RelatedApplication(); |
| application.platform = platform; |
| application.id = id; |
| application.url = url; |
| return application; |
| } |
| |
| /** |
| * Calls filterInstalledApps with the given inputs, and tests that the expected result is |
| * returned. |
| */ |
| private void verifyInstalledApps(RelatedApplication[] manifestRelatedApps, |
| RelatedApplication[] expectedInstalledRelatedApps) { |
| mInstalledAppProvider.filterInstalledApps( |
| manifestRelatedApps, new InstalledAppProvider.FilterInstalledAppsResponse() { |
| @Override |
| public void call(RelatedApplication[] installedRelatedApps) { |
| Assert.assertEquals( |
| expectedInstalledRelatedApps.length, installedRelatedApps.length); |
| |
| for (int i = 0; i < installedRelatedApps.length; i++) { |
| Assert.assertEquals( |
| expectedInstalledRelatedApps[i], installedRelatedApps[i]); |
| } |
| } |
| }); |
| } |
| |
| @Before |
| public void setUp() { |
| mPackageManager = new FakePackageManager(); |
| RuntimeEnvironment.setRobolectricPackageManager(mPackageManager); |
| mFrameUrlDelegate = new FakeFrameUrlDelegate(URL_ON_ORIGIN); |
| mInstalledAppProvider = |
| new InstalledAppProviderTestImpl(mFrameUrlDelegate, RuntimeEnvironment.application); |
| } |
| |
| /** |
| * Origin of the page using the API is missing certain parts of the URI. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOriginMissingParts() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| |
| mFrameUrlDelegate.setFrameUrl(ORIGIN_MISSING_SCHEME); |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| |
| mFrameUrlDelegate.setFrameUrl(ORIGIN_MISSING_HOST); |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Incognito mode with one related Android app. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testIncognitoWithOneInstalledRelatedApp() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| |
| mFrameUrlDelegate.setIncognito(true); |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * No related Android apps. |
| * |
| * An Android app relates to the web app, but not mutual. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testNoRelatedApps() { |
| // The web manifest has no related apps. |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] {}; |
| |
| // One Android app is installed named |PACKAGE_NAME_1|. It has a related web app with origin |
| // |ORIGIN|. |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app with no id (package name). |
| * |
| * An Android app relates to the web app, but not mutual. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOneRelatedAppNoId() { |
| RelatedApplication manifestRelatedApps[] = |
| new RelatedApplication[] {createRelatedApplication(PLATFORM_ANDROID, null, null)}; |
| |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related app (from a non-Android platform). |
| * |
| * An Android app with the same id relates to the web app. This should be ignored since the |
| * manifest doesn't mention the Android app. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOneRelatedNonAndroidApp() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_OTHER, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app; Android app is not installed. |
| * |
| * Another Android app relates to the web app, but not mutual. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOneRelatedAppNotInstalled() { |
| // The web manifest has a related Android app named |PACKAGE_NAME_1|. |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| // One Android app is installed named |PACKAGE_NAME_2|. It has a related web app with origin |
| // |ORIGIN|. |
| setAssetStatement(PACKAGE_NAME_2, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app manifest has an asset_statements key, but the resource it links to is missing. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOneRelatedAppBrokenAssetStatementsResource() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| Bundle metaData = createMetaData(ASSET_STATEMENTS_KEY, 0x1234); |
| String statements = |
| "[" + createAssetStatement(NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN) + "]"; |
| FakeResources resources = new FakeResources(0x4321, statements); |
| mPackageManager.setMetaDataAndResourcesForTest(PACKAGE_NAME_1, metaData, resources); |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app; Android app is not mutually related (has no asset_statements). |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOneRelatedAppNoAssetStatements() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setStringResource(PACKAGE_NAME_1, null, null); |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app; Android app is related to other origins. |
| * |
| * Tests three cases: |
| * - The Android app is related to a web app with a different scheme. |
| * - The Android app is related to a web app with a different host. |
| * - The Android app is related to a web app with a different port. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOneRelatedAppRelatedToDifferentOrigins() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_DIFFERENT_SCHEME); |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_DIFFERENT_HOST); |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_DIFFERENT_PORT); |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app; Android app is installed and mutually related. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testOneInstalledRelatedApp() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Change the frame URL and ensure the app relates to the new URL, not the old one. |
| * |
| * This simulates navigating the frame while keeping the same Mojo service open. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testDynamicallyChangingUrl() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_DIFFERENT_SCHEME); |
| |
| // Should be empty, since Android app does not relate to this frame's origin. |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| |
| // Simulate a navigation to a different origin. |
| mFrameUrlDelegate.setFrameUrl(ORIGIN_DIFFERENT_SCHEME); |
| |
| // Now the result should include the Android app that relates to the new origin. |
| expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| |
| // Simulate the native RenderFrameHost disappearing. |
| mFrameUrlDelegate.setFrameUrl(null); |
| |
| expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app (installed and mutually related), with a non-null URL field. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testInstalledRelatedAppWithUrl() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, URL_UNRELATED)}; |
| |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app; Android app is related to multiple origins. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testMultipleAssetStatements() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| // Create an asset_statements field with multiple statements. The second one matches the web |
| // app. |
| String statements = "[" |
| + createAssetStatement( |
| NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_DIFFERENT_HOST) |
| + ", " + createAssetStatement(NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN) |
| + "]"; |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statements); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * A JSON syntax error in the Android app's asset statement. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementSyntaxError() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String statements = "[{\"target\" {}}]"; |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statements); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * The Android app's asset statement is not an array. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementNotArray() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String statement = createAssetStatement(NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statement); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * The Android app's asset statement array contains non-objects. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementArrayNoObjects() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String statements = "[" |
| + createAssetStatement(NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN) + ", 4]"; |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statements); |
| |
| // Expect it to ignore the integer and successfully parse the valid object. |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app has no "relation" in the asset statement. |
| * |
| * Currently, the relation string (in the Android package's asset statement) is ignored, so the |
| * app is still returned as "installed". |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementNoRelation() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String statements = String.format( |
| "[{\"target\": {\"namespace\": \"%s\", \"site\": \"%s\"}}]", NAMESPACE_WEB, ORIGIN); |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statements); |
| |
| // TODO(mgiuca): [Spec issue] Should we require a specific relation string, rather than any |
| // or no relation? |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app is related with a non-standard relation. |
| * |
| * Currently, the relation string (in the Android package's asset statement) is ignored, so any |
| * will do. Is this desirable, or do we want to require a specific relation string? |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementNonStandardRelation() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, "nonstandard/relation", ORIGIN); |
| |
| // TODO(mgiuca): [Spec issue] Should we require a specific relation string, rather than any |
| // or no relation? |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app has no "target" in the asset statement. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementNoTarget() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String statements = String.format("[{\"relation\": [\"%s\"]}]", RELATION_HANDLE_ALL_URLS); |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statements); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app has no "namespace" in the asset statement. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementNoNamespace() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String statements = |
| String.format("[{\"relation\": [\"%s\"], \"target\": {\"site\": \"%s\"}}]", |
| RELATION_HANDLE_ALL_URLS, ORIGIN); |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statements); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app is related, but not to the web namespace. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testNonWebAssetStatement() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement(PACKAGE_NAME_1, "play", RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app has no "site" in the asset statement. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementNoSite() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String statements = |
| String.format("[{\"relation\": [\"%s\"], \"target\": {\"namespace\": \"%s\"}}]", |
| RELATION_HANDLE_ALL_URLS, NAMESPACE_WEB); |
| setStringResource(PACKAGE_NAME_1, ASSET_STATEMENTS_KEY, statements); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app has a syntax error in the "site" field of the asset statement. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementSiteSyntaxError() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_SYNTAX_ERROR); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Android app has a "site" field missing certain parts of the URI (scheme, host, port). |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementSiteMissingParts() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_MISSING_SCHEME); |
| RelatedApplication[] expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_MISSING_HOST); |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| |
| setAssetStatement( |
| PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN_MISSING_PORT); |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app; Android app is related with a path part in the "site" field. |
| * |
| * The path part shouldn't really be there (according to the Digital Asset Links spec), but if |
| * it is, we are lenient and just ignore it (matching only the origin). |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testAssetStatementSiteHasPath() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| String site = ORIGIN + "/path"; |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, site); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * One related Android app; Android app is installed and mutually related. |
| * |
| * Another Android app relates to the web app, but not mutual. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testExtraInstalledApp() { |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| setAssetStatement(PACKAGE_NAME_2, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Two related Android apps; Android apps both installed and mutually related. |
| * |
| * Web app also related to an app with the same name on another platform, and another Android |
| * app which is not installed. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testMultipleInstalledRelatedApps() { |
| RelatedApplication[] manifestRelatedApps = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null), |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_2, null), |
| createRelatedApplication(PLATFORM_OTHER, PACKAGE_NAME_2, null), |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_3, null)}; |
| |
| setAssetStatement(PACKAGE_NAME_2, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| setAssetStatement(PACKAGE_NAME_3, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| RelatedApplication[] expectedInstalledRelatedApps = |
| new RelatedApplication[] {manifestRelatedApps[1], manifestRelatedApps[3]}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| } |
| |
| /** |
| * Tests the pseudo-random artificial delay to counter a timing attack. |
| */ |
| @Test |
| @Feature({"InstalledApp"}) |
| public void testArtificialDelay() { |
| byte[] salt = {0x64, 0x09, -0x68, -0x25, 0x70, 0x11, 0x25, 0x24, 0x68, -0x1a, 0x08, 0x79, |
| -0x12, -0x50, 0x3b, -0x57, -0x17, -0x4d, 0x46, 0x02}; |
| PackageHash.setGlobalSaltForTesting(salt); |
| setAssetStatement(PACKAGE_NAME_1, NAMESPACE_WEB, RELATION_HANDLE_ALL_URLS, ORIGIN); |
| |
| // Installed app. |
| RelatedApplication manifestRelatedApps[] = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_1, null)}; |
| RelatedApplication[] expectedInstalledRelatedApps = manifestRelatedApps; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| // This expectation is based on HMAC_SHA256(salt, packageName encoded in UTF-8), taking the |
| // low 10 bits of the first two bytes of the result / 100. |
| Assert.assertEquals(2, mInstalledAppProvider.getLastDelayMillis()); |
| |
| // Non-installed app. |
| manifestRelatedApps = new RelatedApplication[] { |
| createRelatedApplication(PLATFORM_ANDROID, PACKAGE_NAME_2, null)}; |
| expectedInstalledRelatedApps = new RelatedApplication[] {}; |
| verifyInstalledApps(manifestRelatedApps, expectedInstalledRelatedApps); |
| // This expectation is based on HMAC_SHA256(salt, packageName encoded in UTF-8), taking the |
| // low 10 bits of the first two bytes of the result / 100. |
| Assert.assertEquals(5, mInstalledAppProvider.getLastDelayMillis()); |
| } |
| } |