Index: content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java b/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..78f92a8ec7efea7947c5a6f1f9cb890e58927672 |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java |
@@ -0,0 +1,146 @@ |
+// Copyright 2016 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.PackageManager; |
+import android.content.pm.PackageManager.NameNotFoundException; |
+import android.content.res.Resources; |
+ |
+import org.chromium.mojo.system.MojoException; |
+import org.chromium.mojom.content.InstalledAppProvider; |
+import org.chromium.mojom.content.RelatedApplication; |
+ |
+import org.json.JSONException; |
+import org.json.JSONObject; |
+ |
+import java.net.MalformedURLException; |
+import java.net.URL; |
+import java.util.ArrayList; |
+import java.util.Arrays; |
+import java.util.Collections; |
+import java.util.List; |
+ |
+/** |
+ * Android implementation of the InstalledAppProvider service defined in |
+ * content/common/installedapp/installed_app_provider.mojom. |
+ */ |
+public class InstalledAppProviderImpl implements InstalledAppProvider { |
+ private Context mContext; |
+ private static final String ASSET_DESCRIPTOR_FIELD_TARGET = "target"; |
+ private static final String ASSET_DESCRIPTOR_WEB = "web"; |
+ private static final String ASSET_DESCRIPTOR_FIELD_SITE = "site"; |
+ private static final String ASSET_DESCRIPTOR_FIELD_NAMESPACE = "namespace"; |
+ private static final String ASSET_STATEMENTS_KEY = "asset_statements"; |
+ private static final String RELATED_APP_ANDROID_PLATFORM = "play"; |
+ |
+ public InstalledAppProviderImpl(Context context) { |
+ mContext = context; |
+ } |
+ |
+ @Override |
+ public void filterInstalledApps( |
+ RelatedApplication[] apps, String origin, FilterInstalledAppsResponse callback) { |
+ callback.call(filterAndroidInstalledApps(apps, origin)); |
+ } |
+ |
+ public RelatedApplication[] filterAndroidInstalledApps( |
+ RelatedApplication[] apps, String origin) { |
+ ArrayList<RelatedApplication> installedApps = new ArrayList<RelatedApplication>(); |
+ PackageManager pm = mContext.getPackageManager(); |
+ for (RelatedApplication app : apps) { |
+ // If the package is of type "play" and origin is associated with package, |
+ // add the package to the list of valid packages. |
+ if (app.platform.equals(RELATED_APP_ANDROID_PLATFORM) |
+ && isOriginAssociatedWithPackage(app.id, origin, pm)) { |
+ installedApps.add(app); |
+ } |
+ } |
+ RelatedApplication installedAppsArray[] = new RelatedApplication[installedApps.size()]; |
+ installedAppsArray = installedApps.toArray(installedAppsArray); |
+ return installedAppsArray; |
+ } |
+ |
+ @Override |
+ public void close() {} |
+ |
+ @Override |
+ public void onConnectionError(MojoException e) {} |
+ |
+ private static List<String> getAssociations(String packageName, PackageManager pm) { |
+ Resources resources; |
+ |
+ try { |
+ resources = pm.getResourcesForApplication(packageName); |
+ } catch (NameNotFoundException e) { |
+ return Collections.<String>emptyList(); |
+ } |
+ |
+ if (resources == null) { |
+ return Collections.<String>emptyList(); |
+ } |
+ |
+ int identifier = resources.getIdentifier(ASSET_STATEMENTS_KEY, "array", packageName); |
+ if (identifier == 0) { |
+ return Collections.<String>emptyList(); |
+ } |
+ return Arrays.asList(resources.getStringArray(identifier)); |
+ } |
+ |
+ private static boolean isOriginAssociatedWithPackage( |
+ String packageName, String origin, PackageManager pm) { |
+ List<String> associations = getAssociations(packageName, pm); |
+ URL originURL; |
+ try { |
+ originURL = new URL(origin); |
+ } catch (MalformedURLException e) { |
+ return false; |
+ } |
+ |
+ for (String statementString : associations) { |
+ try { |
+ JSONObject statement = new JSONObject(statementString); |
+ String assetDescriptorJson = statement.getString(ASSET_DESCRIPTOR_FIELD_TARGET); |
+ JSONObject assetJson = new JSONObject(assetDescriptorJson); |
+ |
+ // If it is not a web appet, skip it. |
+ if (!isAssetIsWeb(assetJson)) { |
+ continue; |
+ } |
+ |
+ URL assetURL; |
+ try { |
+ assetURL = new URL(assetJson.getString(ASSET_DESCRIPTOR_FIELD_SITE)); |
+ } catch (MalformedURLException e) { |
+ continue; |
+ } |
+ |
+ // The URL is considered equivalent if the scheme, host, and port match, according |
+ // to the DigitalAssetLinks v1 spec. |
+ if (statementTargetMatches(originURL, assetURL)) { |
+ return true; |
+ } |
+ } catch (JSONException e) { |
+ continue; |
+ } |
+ } |
+ |
+ return false; |
+ } |
+ |
+ private static boolean isAssetIsWeb(JSONObject asset) { |
+ try { |
+ return asset.getString(ASSET_DESCRIPTOR_FIELD_NAMESPACE).equals(ASSET_DESCRIPTOR_WEB); |
+ } catch (JSONException e) { |
+ return false; |
+ } |
+ } |
+ |
+ private static boolean statementTargetMatches(URL originURL, URL assetURL) { |
+ return (assetURL.getProtocol().equals(originURL.getProtocol()) |
+ && assetURL.getHost().equals(originURL.getHost()) |
+ && assetURL.getPort() == originURL.getPort()); |
+ } |
+} |