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

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

Powered by Google App Engine
This is Rietveld 408576698