| OLD | NEW |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.android_webview; | 5 package org.chromium.android_webview; |
| 6 | 6 |
| 7 import android.content.Context; | |
| 8 import android.content.res.AssetManager; | 7 import android.content.res.AssetManager; |
| 9 import android.net.Uri; | 8 import android.net.Uri; |
| 10 import android.util.Log; | 9 import android.util.Log; |
| 11 import android.util.TypedValue; | 10 import android.util.TypedValue; |
| 12 | 11 |
| 12 import org.chromium.base.ContextUtils; |
| 13 import org.chromium.base.annotations.CalledByNative; | 13 import org.chromium.base.annotations.CalledByNative; |
| 14 import org.chromium.base.annotations.JNINamespace; | 14 import org.chromium.base.annotations.JNINamespace; |
| 15 | 15 |
| 16 import java.io.IOException; | 16 import java.io.IOException; |
| 17 import java.io.InputStream; | 17 import java.io.InputStream; |
| 18 import java.net.URLConnection; | 18 import java.net.URLConnection; |
| 19 import java.util.List; | 19 import java.util.List; |
| 20 | 20 |
| 21 /** | 21 /** |
| 22 * Implements the Java side of Android URL protocol jobs. | 22 * Implements the Java side of Android URL protocol jobs. |
| 23 * See android_protocol_handler.cc. | 23 * See android_protocol_handler.cc. |
| 24 */ | 24 */ |
| 25 @JNINamespace("android_webview") | 25 @JNINamespace("android_webview") |
| 26 public class AndroidProtocolHandler { | 26 public class AndroidProtocolHandler { |
| 27 private static final String TAG = "AndroidProtocolHandler"; | 27 private static final String TAG = "AndroidProtocolHandler"; |
| 28 | 28 |
| 29 // Supported URL schemes. This needs to be kept in sync with | 29 // Supported URL schemes. This needs to be kept in sync with |
| 30 // clank/native/framework/chrome/url_request_android_job.cc. | 30 // clank/native/framework/chrome/url_request_android_job.cc. |
| 31 private static final String FILE_SCHEME = "file"; | 31 private static final String FILE_SCHEME = "file"; |
| 32 private static final String CONTENT_SCHEME = "content"; | 32 private static final String CONTENT_SCHEME = "content"; |
| 33 | 33 |
| 34 /** | 34 /** |
| 35 * Open an InputStream for an Android resource. | 35 * Open an InputStream for an Android resource. |
| 36 * @param context The context manager. | 36 * |
| 37 * @param url The url to load. | 37 * @param url The url to load. |
| 38 * @return An InputStream to the Android resource. | 38 * @return An InputStream to the Android resource. |
| 39 */ | 39 */ |
| 40 @CalledByNative | 40 @CalledByNative |
| 41 public static InputStream open(Context context, String url) { | 41 public static InputStream open(String url) { |
| 42 Uri uri = verifyUrl(url); | 42 Uri uri = verifyUrl(url); |
| 43 if (uri == null) { | 43 if (uri == null) { |
| 44 return null; | 44 return null; |
| 45 } | 45 } |
| 46 try { | 46 try { |
| 47 String path = uri.getPath(); | 47 String path = uri.getPath(); |
| 48 if (uri.getScheme().equals(FILE_SCHEME)) { | 48 if (uri.getScheme().equals(FILE_SCHEME)) { |
| 49 if (path.startsWith(nativeGetAndroidAssetPath())) { | 49 if (path.startsWith(nativeGetAndroidAssetPath())) { |
| 50 return openAsset(context, uri); | 50 return openAsset(uri); |
| 51 } else if (path.startsWith(nativeGetAndroidResourcePath())) { | 51 } else if (path.startsWith(nativeGetAndroidResourcePath())) { |
| 52 return openResource(context, uri); | 52 return openResource(uri); |
| 53 } | 53 } |
| 54 } else if (uri.getScheme().equals(CONTENT_SCHEME)) { | 54 } else if (uri.getScheme().equals(CONTENT_SCHEME)) { |
| 55 return openContent(context, uri); | 55 return openContent(uri); |
| 56 } | 56 } |
| 57 } catch (Exception ex) { | 57 } catch (Exception ex) { |
| 58 Log.e(TAG, "Error opening inputstream: " + url); | 58 Log.e(TAG, "Error opening inputstream: " + url); |
| 59 } | 59 } |
| 60 return null; | 60 return null; |
| 61 } | 61 } |
| 62 | 62 |
| 63 // Assumes input string is in the format "foo.bar.baz" and strips out the la
st component. | 63 // Assumes input string is in the format "foo.bar.baz" and strips out the la
st component. |
| 64 // Returns null on failure. | 64 // Returns null on failure. |
| 65 private static String removeOneSuffix(String input) { | 65 private static String removeOneSuffix(String input) { |
| 66 if (input == null) return null; | 66 if (input == null) return null; |
| 67 int lastDotIndex = input.lastIndexOf('.'); | 67 int lastDotIndex = input.lastIndexOf('.'); |
| 68 if (lastDotIndex == -1) return null; | 68 if (lastDotIndex == -1) return null; |
| 69 return input.substring(0, lastDotIndex); | 69 return input.substring(0, lastDotIndex); |
| 70 } | 70 } |
| 71 | 71 |
| 72 private static Class<?> getClazz(Context context, String packageName, String
assetType) | 72 private static Class<?> getClazz(String assetType) throws ClassNotFoundExcep
tion { |
| 73 throws ClassNotFoundException { | 73 return ContextUtils.getApplicationContext().getClassLoader().loadClass( |
| 74 return context.getClassLoader().loadClass(packageName + ".R$" + assetTyp
e); | 74 ContextUtils.getApplicationContext().getPackageName() + ".R$" +
assetType); |
| 75 } | 75 } |
| 76 | 76 |
| 77 private static int getFieldId(Context context, String assetType, String asse
tName) | 77 private static int getFieldId(String assetType, String assetName) |
| 78 throws ClassNotFoundException, NoSuchFieldException, IllegalAccessExcept
ion { | 78 throws ClassNotFoundException, NoSuchFieldException, IllegalAccessEx
ception { |
| 79 Class<?> clazz = null; | 79 Class<?> clazz = null; |
| 80 try { | 80 try { |
| 81 clazz = getClazz(context, context.getPackageName(), assetType); | 81 clazz = getClazz(assetType); |
| 82 } catch (ClassNotFoundException e) { | 82 } catch (ClassNotFoundException e) { |
| 83 // Strip out components from the end so gradle generated application
suffix such as | 83 // Strip out components from the end so gradle generated application
suffix such as |
| 84 // com.example.my.pkg.pro works. This is by no means bulletproof. | 84 // com.example.my.pkg.pro works. This is by no means bulletproof. |
| 85 String packageName = context.getPackageName(); | 85 String packageName = ContextUtils.getApplicationContext().getPackage
Name(); |
| 86 while (clazz == null) { | 86 while (clazz == null) { |
| 87 packageName = removeOneSuffix(packageName); | 87 packageName = removeOneSuffix(packageName); |
| 88 // Throw original exception which contains the entire package id
. | 88 // Throw original exception which contains the entire package id
. |
| 89 if (packageName == null) throw e; | 89 if (packageName == null) throw e; |
| 90 try { | 90 try { |
| 91 clazz = getClazz(context, packageName, assetType); | 91 clazz = getClazz(assetType); |
| 92 } catch (ClassNotFoundException ignored) { | 92 } catch (ClassNotFoundException ignored) { |
| 93 // Strip and try again. | 93 // Strip and try again. |
| 94 } | 94 } |
| 95 } | 95 } |
| 96 } | 96 } |
| 97 | 97 |
| 98 java.lang.reflect.Field field = clazz.getField(assetName); | 98 java.lang.reflect.Field field = clazz.getField(assetName); |
| 99 int id = field.getInt(null); | 99 int id = field.getInt(null); |
| 100 return id; | 100 return id; |
| 101 } | 101 } |
| 102 | 102 |
| 103 private static int getValueType(Context context, int fieldId) { | 103 private static int getValueType(int fieldId) { |
| 104 TypedValue value = new TypedValue(); | 104 TypedValue value = new TypedValue(); |
| 105 context.getResources().getValue(fieldId, value, true); | 105 ContextUtils.getApplicationContext().getResources().getValue(fieldId, va
lue, true); |
| 106 return value.type; | 106 return value.type; |
| 107 } | 107 } |
| 108 | 108 |
| 109 private static InputStream openResource(Context context, Uri uri) { | 109 private static InputStream openResource(Uri uri) { |
| 110 assert uri.getScheme().equals(FILE_SCHEME); | 110 assert uri.getScheme().equals(FILE_SCHEME); |
| 111 assert uri.getPath() != null; | 111 assert uri.getPath() != null; |
| 112 assert uri.getPath().startsWith(nativeGetAndroidResourcePath()); | 112 assert uri.getPath().startsWith(nativeGetAndroidResourcePath()); |
| 113 // The path must be of the form "/android_res/asset_type/asset_name.ext"
. | 113 // The path must be of the form "/android_res/asset_type/asset_name.ext"
. |
| 114 List<String> pathSegments = uri.getPathSegments(); | 114 List<String> pathSegments = uri.getPathSegments(); |
| 115 if (pathSegments.size() != 3) { | 115 if (pathSegments.size() != 3) { |
| 116 Log.e(TAG, "Incorrect resource path: " + uri); | 116 Log.e(TAG, "Incorrect resource path: " + uri); |
| 117 return null; | 117 return null; |
| 118 } | 118 } |
| 119 String assetPath = pathSegments.get(0); | 119 String assetPath = pathSegments.get(0); |
| 120 String assetType = pathSegments.get(1); | 120 String assetType = pathSegments.get(1); |
| 121 String assetName = pathSegments.get(2); | 121 String assetName = pathSegments.get(2); |
| 122 if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) { | 122 if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) { |
| 123 Log.e(TAG, "Resource path does not start with " + nativeGetAndroidRe
sourcePath() | 123 Log.e(TAG, "Resource path does not start with " + nativeGetAndroidRe
sourcePath() |
| 124 + ": " + uri); | 124 + ": " + uri); |
| 125 return null; | 125 return null; |
| 126 } | 126 } |
| 127 // Drop the file extension. | 127 // Drop the file extension. |
| 128 assetName = assetName.split("\\.")[0]; | 128 assetName = assetName.split("\\.")[0]; |
| 129 try { | 129 try { |
| 130 // Use the application context for resolving the resource package na
me so that we do | 130 int fieldId = getFieldId(assetType, assetName); |
| 131 // not use the browser's own resources. Note that if 'context' here
belongs to the | 131 int valueType = getValueType(fieldId); |
| 132 // test suite, it does not have a separate application context. In t
hat case we use | |
| 133 // the original context object directly. | |
| 134 if (context.getApplicationContext() != null) { | |
| 135 context = context.getApplicationContext(); | |
| 136 } | |
| 137 int fieldId = getFieldId(context, assetType, assetName); | |
| 138 int valueType = getValueType(context, fieldId); | |
| 139 if (valueType == TypedValue.TYPE_STRING) { | 132 if (valueType == TypedValue.TYPE_STRING) { |
| 140 return context.getResources().openRawResource(fieldId); | 133 return ContextUtils.getApplicationContext().getResources().openR
awResource(fieldId); |
| 141 } else { | 134 } else { |
| 142 Log.e(TAG, "Asset not of type string: " + uri); | 135 Log.e(TAG, "Asset not of type string: " + uri); |
| 143 return null; | 136 return null; |
| 144 } | 137 } |
| 145 } catch (ClassNotFoundException e) { | 138 } catch (ClassNotFoundException e) { |
| 146 Log.e(TAG, "Unable to open resource URL: " + uri, e); | 139 Log.e(TAG, "Unable to open resource URL: " + uri, e); |
| 147 return null; | 140 return null; |
| 148 } catch (NoSuchFieldException e) { | 141 } catch (NoSuchFieldException e) { |
| 149 Log.e(TAG, "Unable to open resource URL: " + uri, e); | 142 Log.e(TAG, "Unable to open resource URL: " + uri, e); |
| 150 return null; | 143 return null; |
| 151 } catch (IllegalAccessException e) { | 144 } catch (IllegalAccessException e) { |
| 152 Log.e(TAG, "Unable to open resource URL: " + uri, e); | 145 Log.e(TAG, "Unable to open resource URL: " + uri, e); |
| 153 return null; | 146 return null; |
| 154 } | 147 } |
| 155 } | 148 } |
| 156 | 149 |
| 157 private static InputStream openAsset(Context context, Uri uri) { | 150 private static InputStream openAsset(Uri uri) { |
| 158 assert uri.getScheme().equals(FILE_SCHEME); | 151 assert uri.getScheme().equals(FILE_SCHEME); |
| 159 assert uri.getPath() != null; | 152 assert uri.getPath() != null; |
| 160 assert uri.getPath().startsWith(nativeGetAndroidAssetPath()); | 153 assert uri.getPath().startsWith(nativeGetAndroidAssetPath()); |
| 161 String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), ""
); | 154 String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), ""
); |
| 162 try { | 155 try { |
| 163 AssetManager assets = context.getAssets(); | 156 AssetManager assets = ContextUtils.getApplicationContext().getAssets
(); |
| 164 return assets.open(path, AssetManager.ACCESS_STREAMING); | 157 return assets.open(path, AssetManager.ACCESS_STREAMING); |
| 165 } catch (IOException e) { | 158 } catch (IOException e) { |
| 166 Log.e(TAG, "Unable to open asset URL: " + uri); | 159 Log.e(TAG, "Unable to open asset URL: " + uri); |
| 167 return null; | 160 return null; |
| 168 } | 161 } |
| 169 } | 162 } |
| 170 | 163 |
| 171 private static InputStream openContent(Context context, Uri uri) { | 164 private static InputStream openContent(Uri uri) { |
| 172 assert uri.getScheme().equals(CONTENT_SCHEME); | 165 assert uri.getScheme().equals(CONTENT_SCHEME); |
| 173 try { | 166 try { |
| 174 return context.getContentResolver().openInputStream(uri); | 167 return ContextUtils.getApplicationContext().getContentResolver().ope
nInputStream(uri); |
| 175 } catch (Exception e) { | 168 } catch (Exception e) { |
| 176 Log.e(TAG, "Unable to open content URL: " + uri); | 169 Log.e(TAG, "Unable to open content URL: " + uri); |
| 177 return null; | 170 return null; |
| 178 } | 171 } |
| 179 } | 172 } |
| 180 | 173 |
| 181 /** | 174 /** |
| 182 * Determine the mime type for an Android resource. | 175 * Determine the mime type for an Android resource. |
| 183 * @param context The context manager. | 176 * |
| 184 * @param stream The opened input stream which to examine. | 177 * @param stream The opened input stream which to examine. |
| 185 * @param url The url from which the stream was opened. | 178 * @param url The url from which the stream was opened. |
| 186 * @return The mime type or null if the type is unknown. | 179 * @return The mime type or null if the type is unknown. |
| 187 */ | 180 */ |
| 188 @CalledByNative | 181 @CalledByNative |
| 189 public static String getMimeType(Context context, InputStream stream, String
url) { | 182 public static String getMimeType(InputStream stream, String url) { |
| 190 Uri uri = verifyUrl(url); | 183 Uri uri = verifyUrl(url); |
| 191 if (uri == null) { | 184 if (uri == null) { |
| 192 return null; | 185 return null; |
| 193 } | 186 } |
| 194 try { | 187 try { |
| 195 String path = uri.getPath(); | 188 String path = uri.getPath(); |
| 196 // The content URL type can be queried directly. | 189 // The content URL type can be queried directly. |
| 197 if (uri.getScheme().equals(CONTENT_SCHEME)) { | 190 if (uri.getScheme().equals(CONTENT_SCHEME)) { |
| 198 return context.getContentResolver().getType(uri); | 191 return ContextUtils.getApplicationContext().getContentResolver()
.getType(uri); |
| 199 // Asset files may have a known extension. | 192 // Asset files may have a known extension. |
| 200 } else if (uri.getScheme().equals(FILE_SCHEME) | 193 } else if (uri.getScheme().equals(FILE_SCHEME) |
| 201 && path.startsWith(nativeGetAndroidAssetPath())) { | 194 && path.startsWith(nativeGetAndroidAssetPath())) { |
| 202 String mimeType = URLConnection.guessContentTypeFromName(path); | 195 String mimeType = URLConnection.guessContentTypeFromName(path); |
| 203 if (mimeType != null) { | 196 if (mimeType != null) { |
| 204 return mimeType; | 197 return mimeType; |
| 205 } | 198 } |
| 206 } | 199 } |
| 207 } catch (Exception ex) { | 200 } catch (Exception ex) { |
| 208 Log.e(TAG, "Unable to get mime type" + url); | 201 Log.e(TAG, "Unable to get mime type" + url); |
| 209 return null; | 202 return null; |
| 210 } | 203 } |
| 211 // Fall back to sniffing the type from the stream. | 204 // Fall back to sniffing the type from the stream. |
| 212 try { | 205 try { |
| 213 return URLConnection.guessContentTypeFromStream(stream); | 206 return URLConnection.guessContentTypeFromStream(stream); |
| 214 } catch (IOException e) { | 207 } catch (IOException e) { |
| 215 return null; | 208 return null; |
| 216 } | 209 } |
| 217 } | 210 } |
| 218 | 211 |
| 219 /** | 212 /** |
| 220 * Make sure the given string URL is correctly formed and parse it into a Ur
i. | 213 * Make sure the given string URL is correctly formed and parse it into a Ur
i. |
| 214 * |
| 221 * @return a Uri instance, or null if the URL was invalid. | 215 * @return a Uri instance, or null if the URL was invalid. |
| 222 */ | 216 */ |
| 223 private static Uri verifyUrl(String url) { | 217 private static Uri verifyUrl(String url) { |
| 224 if (url == null) { | 218 if (url == null) { |
| 225 return null; | 219 return null; |
| 226 } | 220 } |
| 227 Uri uri = Uri.parse(url); | 221 Uri uri = Uri.parse(url); |
| 228 if (uri == null) { | 222 if (uri == null) { |
| 229 Log.e(TAG, "Malformed URL: " + url); | 223 Log.e(TAG, "Malformed URL: " + url); |
| 230 return null; | 224 return null; |
| 231 } | 225 } |
| 232 String path = uri.getPath(); | 226 String path = uri.getPath(); |
| 233 if (path == null || path.length() == 0) { | 227 if (path == null || path.length() == 0) { |
| 234 Log.e(TAG, "URL does not have a path: " + url); | 228 Log.e(TAG, "URL does not have a path: " + url); |
| 235 return null; | 229 return null; |
| 236 } | 230 } |
| 237 return uri; | 231 return uri; |
| 238 } | 232 } |
| 239 | 233 |
| 240 /** | 234 private static native String nativeGetAndroidAssetPath(); |
| 241 * Set the context to be used for resolving resource queries. | |
| 242 * @param context Context to be used, or null for the default application | |
| 243 * context. | |
| 244 */ | |
| 245 public static void setResourceContextForTesting(Context context) { | |
| 246 nativeSetResourceContextForTesting(context); | |
| 247 } | |
| 248 | 235 |
| 249 private static native void nativeSetResourceContextForTesting(Context contex
t); | |
| 250 private static native String nativeGetAndroidAssetPath(); | |
| 251 private static native String nativeGetAndroidResourcePath(); | 236 private static native String nativeGetAndroidResourcePath(); |
| 252 } | 237 } |
| OLD | NEW |