1 // Copyright (c) 2012 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.
5 package org.xwalk.core.internal;
7 import android.content.Context;
8 import android.content.res.AssetManager;
9 import android.net.Uri;
10 import android.util.Log;
11 import android.util.TypedValue;
13 import java.io.InputStream;
14 import java.io.IOException;
16 import java.net.URISyntaxException;
17 import java.net.URLConnection;
18 import java.util.List;
20 import org.chromium.base.CalledByNative;
21 import org.chromium.base.JNINamespace;
24 * Implements the Java side of Android URL protocol jobs.
25 * See android_protocol_handler.cc.
27 @JNINamespace("xwalk")
28 class AndroidProtocolHandler {
29 private static final String TAG = "AndroidProtocolHandler";
31 // Supported URL schemes. This needs to be kept in sync with
32 // clank/native/framework/chrome/url_request_android_job.cc.
33 public static final String FILE_SCHEME = "file";
34 private static final String CONTENT_SCHEME = "content";
35 public static final String APP_SCHEME = "app";
36 private static final String APP_SRC = "www";
37 private static final String SCHEME_SEPARATOR = "//";
40 * Open an InputStream for an Android resource.
41 * @param context The context manager.
42 * @param url The url to load.
43 * @return An InputStream to the Android resource.
46 public static InputStream open(Context context, String url) {
47 Uri uri = verifyUrl(url);
52 String path = uri.getPath();
53 if (uri.getScheme().equals(FILE_SCHEME)) {
54 if (path.startsWith(nativeGetAndroidAssetPath())) {
55 return openAsset(context, uri);
56 } else if (path.startsWith(nativeGetAndroidResourcePath())) {
57 return openResource(context, uri);
59 } else if (uri.getScheme().equals(CONTENT_SCHEME)) {
60 return openContent(context, uri);
61 } else if (uri.getScheme().equals(APP_SCHEME)) {
62 // The host should be the same as the lower case of the package
63 // name, otherwise the resource request should be rejected.
64 if (!uri.getHost().equals(context.getPackageName().toLowerCase())) return null;
66 // path == "/" or path == ""
67 if (path.length() <= 1) return null;
69 return openAsset(context, appUriToFileUri(uri));
71 } catch (Exception ex) {
72 Log.e(TAG, "Error opening inputstream: " + url);
78 // Get the asset path of file:///android_asset/* url.
79 public static String getAssetPath(Uri uri) {
80 assert(uri.getScheme().equals(FILE_SCHEME));
81 assert(uri.getPath() != null);
82 assert(uri.getPath().startsWith(nativeGetAndroidAssetPath()));
83 String path = uri.getPath();
84 // Remove duplicate slashes and normalize the URL.
85 path = (new java.io.File(path)).getAbsolutePath();
86 return path.replaceFirst(nativeGetAndroidAssetPath(), "");
89 // Convert app uri to file uri to access the actual files in assets.
90 public static Uri appUriToFileUri(Uri uri) {
91 assert(uri.getScheme().equals(APP_SCHEME));
92 assert(uri.getPath() != null);
95 URI fileUri = new URI(FILE_SCHEME, SCHEME_SEPARATOR +
96 nativeGetAndroidAssetPath() + APP_SRC + uri.getPath(), null);
97 return Uri.parse(fileUri.normalize().toString());
98 } catch (URISyntaxException e) {
99 Log.e(TAG, "Unable to convert app URI to file URI: " + uri, e);
104 static String getUrlContent(Context context, String url) throws IOException {
105 InputStream stream = open(context, url);
106 if (stream == null) {
107 throw new RuntimeException("Failed to open the url: " + url);
112 final int bufferSize = 1024;
113 byte[] buffer = new byte[bufferSize];
115 while ((actualSize = stream.read(buffer, 0, bufferSize)) > 0) {
116 content += new String(buffer, 0, actualSize);
124 private static int getFieldId(Context context, String assetType, String assetName)
125 throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
126 Class<?> d = context.getClassLoader()
127 .loadClass(context.getPackageName() + ".R$" + assetType);
128 java.lang.reflect.Field field = d.getField(assetName);
129 int id = field.getInt(null);
133 private static int getValueType(Context context, int fieldId) {
134 TypedValue value = new TypedValue();
135 context.getResources().getValue(fieldId, value, true);
139 private static InputStream openResource(Context context, Uri uri) {
140 assert uri.getScheme().equals(FILE_SCHEME);
141 assert uri.getPath() != null;
142 assert uri.getPath().startsWith(nativeGetAndroidResourcePath());
143 // The path must be of the form "/android_res/asset_type/asset_name.ext".
144 List<String> pathSegments = uri.getPathSegments();
145 if (pathSegments.size() != 3) {
146 Log.e(TAG, "Incorrect resource path: " + uri);
149 String assetPath = pathSegments.get(0);
150 String assetType = pathSegments.get(1);
151 String assetName = pathSegments.get(2);
152 if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) {
153 Log.e(TAG, "Resource path does not start with " + nativeGetAndroidResourcePath() +
157 // Drop the file extension.
158 assetName = assetName.split("\\.")[0];
160 // Use the application context for resolving the resource package name so that we do
161 // not use the browser's own resources. Note that if 'context' here belongs to the
162 // test suite, it does not have a separate application context. In that case we use
163 // the original context object directly.
164 if (context.getApplicationContext() != null) {
165 context = context.getApplicationContext();
167 int fieldId = getFieldId(context, assetType, assetName);
168 int valueType = getValueType(context, fieldId);
169 if (valueType == TypedValue.TYPE_STRING) {
170 return context.getResources().openRawResource(fieldId);
172 Log.e(TAG, "Asset not of type string: " + uri);
175 } catch (ClassNotFoundException e) {
176 Log.e(TAG, "Unable to open resource URL: " + uri, e);
178 } catch (NoSuchFieldException e) {
179 Log.e(TAG, "Unable to open resource URL: " + uri, e);
181 } catch (IllegalAccessException e) {
182 Log.e(TAG, "Unable to open resource URL: " + uri, e);
187 private static InputStream openAsset(Context context, Uri uri) {
188 assert uri.getScheme().equals(FILE_SCHEME);
189 assert uri.getPath() != null;
190 assert uri.getPath().startsWith(nativeGetAndroidAssetPath());
191 String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), "");
193 AssetManager assets = context.getAssets();
194 return assets.open(path, AssetManager.ACCESS_STREAMING);
195 } catch (IOException e) {
196 Log.e(TAG, "Unable to open asset URL: " + uri);
201 private static InputStream openContent(Context context, Uri uri) {
202 assert(uri.getScheme().equals(CONTENT_SCHEME));
204 // We strip the query parameters before opening the stream to
205 // ensure that the URL we try to load exactly matches the URL
206 // we have permission to read.
207 Uri baseUri = stripQueryParameters(uri);
208 return context.getContentResolver().openInputStream(baseUri);
209 } catch (Exception e) {
210 Log.e(TAG, "Unable to open content URL: " + uri);
216 * Determine the mime type for an Android resource.
217 * @param context The context manager.
218 * @param stream The opened input stream which to examine.
219 * @param url The url from which the stream was opened.
220 * @return The mime type or null if the type is unknown.
223 public static String getMimeType(Context context, InputStream stream, String url) {
224 Uri uri = verifyUrl(url);
229 String path = uri.getPath();
230 // The content URL type can be queried directly.
231 if (uri.getScheme().equals(CONTENT_SCHEME)) {
232 return context.getContentResolver().getType(uri);
233 // Asset files may have a known extension.
234 } else if (uri.getScheme().equals(FILE_SCHEME) &&
235 path.startsWith(nativeGetAndroidAssetPath())) {
236 String mimeType = URLConnection.guessContentTypeFromName(path);
237 if (mimeType != null) {
241 } catch (Exception ex) {
242 Log.e(TAG, "Unable to get mime type" + url);
245 // Fall back to sniffing the type from the stream.
247 return URLConnection.guessContentTypeFromStream(stream);
248 } catch (IOException e) {
254 * Get the package name of the current Activity.
255 * @param context The context manager.
256 * @return Package name.
259 public static String getPackageName(Context context) {
261 // Make sure the context is the application context.
262 // Or it will get the wrong package name in shared mode.
263 return context.getPackageName();
264 } catch (Exception ex) {
265 Log.e(TAG, "Unable to get package name");
271 * Make sure the given string URL is correctly formed and parse it into a Uri.
272 * @return a Uri instance, or null if the URL was invalid.
274 private static Uri verifyUrl(String url) {
278 Uri uri = Uri.parse(url);
280 Log.e(TAG, "Malformed URL: " + url);
283 String path = uri.getPath();
284 if (path == null || path.length() == 0) {
285 Log.e(TAG, "URL does not have a path: " + url);
292 * Remove query parameters from a Uri.
293 * @param uri The input uri.
294 * @return The given uri without query parameters.
296 private static Uri stripQueryParameters(Uri uri) {
297 assert(uri.getAuthority() != null);
298 assert(uri.getPath() != null);
299 Uri.Builder builder = new Uri.Builder();
300 builder.scheme(uri.getScheme());
301 builder.encodedAuthority(uri.getAuthority());
302 builder.encodedPath(uri.getPath());
303 return builder.build();
307 * Set the context to be used for resolving resource queries.
308 * @param context Context to be used, or null for the default application
311 public static void setResourceContextForTesting(Context context) {
312 nativeSetResourceContextForTesting(context);
315 private static native void nativeSetResourceContextForTesting(Context context);
316 private static native String nativeGetAndroidAssetPath();
317 private static native String nativeGetAndroidResourcePath();