Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(425)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java

Issue 2706403014: Add Android implementation of navigator.getInstalledRelatedApps. (Closed)
Patch Set: Respond to review (importantly, getting the URL on every request, not caching). Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser.installedapp;
6
7 import android.content.Context;
8 import android.content.pm.ApplicationInfo;
9 import android.content.pm.PackageManager;
10 import android.content.pm.PackageManager.NameNotFoundException;
11 import android.content.res.Resources;
12
13 import org.json.JSONArray;
14 import org.json.JSONException;
15 import org.json.JSONObject;
16
17 import org.chromium.base.Log;
18 import org.chromium.base.VisibleForTesting;
19 import org.chromium.installedapp.mojom.InstalledAppProvider;
20 import org.chromium.installedapp.mojom.RelatedApplication;
21 import org.chromium.mojo.system.MojoException;
22
23 import java.net.URI;
24 import java.net.URISyntaxException;
25 import java.util.ArrayList;
26
27 /**
28 * Android implementation of the InstalledAppProvider service defined in
29 * installed_app_provider.mojom
30 */
31 public class InstalledAppProviderImpl implements InstalledAppProvider {
32 @VisibleForTesting
33 public static final String ASSET_STATEMENTS_KEY = "asset_statements";
34 private static final String ASSET_STATEMENT_FIELD_TARGET = "target";
35 private static final String ASSET_STATEMENT_FIELD_NAMESPACE = "namespace";
36 private static final String ASSET_STATEMENT_FIELD_SITE = "site";
37 @VisibleForTesting
38 public static final String ASSET_STATEMENT_NAMESPACE_WEB = "web";
39 @VisibleForTesting
40 public static final String RELATED_APP_PLATFORM_ANDROID = "play";
41
42 private static final String TAG = "InstalledAppProvider";
43
44 private final PageUrlDelegate mPageUrlDelegate;
45 private final Context mContext;
46
47 /**
48 * Small interface for dynamically getting the URL of the current page.
dcheng 2017/03/21 07:37:50 s/page/frame (and similarly the interface should p
Matt Giuca 2017/03/21 07:46:01 Ack. (Will do tomorrow.)
Matt Giuca 2017/03/22 00:28:47 Done.
49 *
50 * Abstract to allow for testing.
51 */
52 public static interface PageUrlDelegate { public URI getUrl(); }
53
54 public InstalledAppProviderImpl(PageUrlDelegate pageUrlDelegate, Context con text) {
55 mPageUrlDelegate = pageUrlDelegate;
56 mContext = context;
57 }
58
59 @Override
60 public void filterInstalledApps(
61 RelatedApplication[] relatedApps, FilterInstalledAppsResponse callba ck) {
62 URI pageUrl = mPageUrlDelegate.getUrl();
63 ArrayList<RelatedApplication> installedApps = new ArrayList<RelatedAppli cation>();
64 PackageManager pm = mContext.getPackageManager();
65 for (RelatedApplication app : relatedApps) {
66 // If the package is of type "play", it is installed, and the origin is associated with
67 // package, add the package to the list of valid packages.
68 // NOTE: For security, it must not be possible to distinguish (from the response)
69 // between the app not being installed and the origin not being asso ciated with the app
70 // (otherwise, arbitrary websites would be able to test whether un-a ssociated apps are
71 // installed on the user's device).
72 if (app.platform.equals(RELATED_APP_PLATFORM_ANDROID) && app.id != n ull
73 && isAppInstalledAndAssociatedWithOrigin(app.id, pageUrl, pm )) {
74 installedApps.add(app);
75 }
76 }
77 RelatedApplication[] installedAppsArray = new RelatedApplication[install edApps.size()];
78 installedApps.toArray(installedAppsArray);
79 callback.call(installedAppsArray);
80 }
81
82 @Override
83 public void close() {}
84
85 @Override
86 public void onConnectionError(MojoException e) {}
87
88 /**
89 * Determines whether a particular app is installed and matches the origin.
90 *
91 * @param packageName Name of the Android package to check if installed. Ret urns false if the
92 * app is not installed.
93 * @param pageUrl Returns false if the Android package does not declare asso ciation with the
94 * origin of this URL.
95 */
96 private static boolean isAppInstalledAndAssociatedWithOrigin(
97 String packageName, URI pageUrl, PackageManager pm) {
98 // Early-exit if the Android app is not installed.
99 JSONArray statements;
100 try {
101 statements = getAssetStatements(packageName, pm);
102 } catch (NameNotFoundException e) {
103 return false;
104 }
105
106 // The installed Android app has provided us with a list of asset statem ents. If any one of
107 // those statements is a web asset that matches the given origin, return true.
108 for (int i = 0; i < statements.length(); i++) {
109 JSONObject statement;
110 try {
111 statement = statements.getJSONObject(i);
112 } catch (JSONException e) {
113 // If an element is not an object, just ignore it.
114 continue;
115 }
116
117 URI site = getSiteForWebAsset(statement);
118
119 // The URI is considered equivalent if the scheme, host, and port ma tch, according
120 // to the DigitalAssetLinks v1 spec.
121 if (site != null && statementTargetMatches(pageUrl, site)) {
122 return true;
123 }
124 }
125
126 // No asset matched the origin.
127 return false;
128 }
129
130 /**
131 * Gets the asset statements from an Android app's manifest.
132 *
133 * This retrieves the list of statements from the Android app's "asset_state ments" manifest
134 * resource, as specified in Digital Asset Links v1.
135 *
136 * @param packageName Name of the Android package to get statements from.
137 * @return The list of asset statements, parsed from JSON.
138 * @throws NameNotFoundException if the application is not installed.
139 */
140 private static JSONArray getAssetStatements(String packageName, PackageManag er pm)
141 throws NameNotFoundException {
142 // Get the <meta-data> from this app's manifest.
143 // Throws NameNotFoundException if the application is not installed.
144 ApplicationInfo appInfo = pm.getApplicationInfo(packageName, PackageMana ger.GET_META_DATA);
145 int identifier = appInfo.metaData.getInt(ASSET_STATEMENTS_KEY);
146 if (identifier == 0) {
147 return new JSONArray();
148 }
149
150 // Throws NameNotFoundException in the rare case that the application wa s uninstalled since
151 // getting |appInfo| (or resources could not be loaded for some other re ason).
152 Resources resources = pm.getResourcesForApplication(appInfo);
153
154 String statements;
155 try {
156 statements = resources.getString(identifier);
157 } catch (Resources.NotFoundException e) {
158 // This should never happen, but it could if there was a broken APK, so handle it
159 // gracefully without crashing.
160 Log.w(TAG,
161 "Android package " + packageName + " missing asset statement s resource (0x"
162 + Integer.toHexString(identifier) + ").");
163 return new JSONArray();
164 }
165
166 try {
167 return new JSONArray(statements);
168 } catch (JSONException e) {
169 // If the JSON is invalid or not an array, assume it is empty.
170 Log.w(TAG,
171 "Android package " + packageName
172 + " has JSON syntax error in asset statements resour ce (0x"
173 + Integer.toHexString(identifier) + ").");
174 return new JSONArray();
175 }
176 }
177
178 /**
179 * Gets the "site" URI from an Android asset statement.
180 *
181 * @return The site, or null if the asset string was invalid or not related to a web site. This
182 * could be because: the JSON string was invalid, there was no "targ et" field, this was
183 * not a web asset, there was no "site" field, or the "site" field w as invalid.
184 */
185 private static URI getSiteForWebAsset(JSONObject statement) {
186 JSONObject target;
187 try {
188 // Ignore the "relation" field and allow an asset with any relation to this origin.
189 // TODO(mgiuca): [Spec issue] Should we require a specific relation string, rather
190 // than any or no relation?
191 target = statement.getJSONObject(ASSET_STATEMENT_FIELD_TARGET);
192 } catch (JSONException e) {
193 return null;
194 }
195
196 // If it is not a web asset, skip it.
197 if (!isAssetWeb(target)) {
198 return null;
199 }
200
201 try {
202 return new URI(target.getString(ASSET_STATEMENT_FIELD_SITE));
203 } catch (JSONException | URISyntaxException e) {
204 return null;
205 }
206 }
207
208 /**
209 * Determines whether an Android asset statement is for a website.
210 *
211 * @param target The "target" field of the asset statement.
212 */
213 private static boolean isAssetWeb(JSONObject target) {
214 String namespace;
215 try {
216 namespace = target.getString(ASSET_STATEMENT_FIELD_NAMESPACE);
217 } catch (JSONException e) {
218 return false;
219 }
220
221 return namespace.equals(ASSET_STATEMENT_NAMESPACE_WEB);
222 }
223
224 private static boolean statementTargetMatches(URI pageUrl, URI assetUrl) {
225 if (assetUrl.getScheme() == null || assetUrl.getAuthority() == null) {
226 return false;
227 }
228
229 return assetUrl.getScheme().equals(pageUrl.getScheme())
230 && assetUrl.getAuthority().equals(pageUrl.getAuthority());
231 }
232 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698