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; |
7 import android.content.res.AssetManager; | 8 import android.content.res.AssetManager; |
8 import android.net.Uri; | 9 import android.net.Uri; |
9 import android.util.Log; | 10 import android.util.Log; |
10 import android.util.TypedValue; | 11 import android.util.TypedValue; |
11 | 12 |
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 * | 36 * @param context The context manager. |
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(String url) { | 41 public static InputStream open(Context context, 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(uri); | 50 return openAsset(context, uri); |
51 } else if (path.startsWith(nativeGetAndroidResourcePath())) { | 51 } else if (path.startsWith(nativeGetAndroidResourcePath())) { |
52 return openResource(uri); | 52 return openResource(context, uri); |
53 } | 53 } |
54 } else if (uri.getScheme().equals(CONTENT_SCHEME)) { | 54 } else if (uri.getScheme().equals(CONTENT_SCHEME)) { |
55 return openContent(uri); | 55 return openContent(context, 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(String assetType) throws ClassNotFoundExcep
tion { | 72 private static Class<?> getClazz(Context context, String packageName, String
assetType) |
73 return ContextUtils.getApplicationContext().getClassLoader().loadClass( | 73 throws ClassNotFoundException { |
74 ContextUtils.getApplicationContext().getPackageName() + ".R$" +
assetType); | 74 return context.getClassLoader().loadClass(packageName + ".R$" + assetTyp
e); |
75 } | 75 } |
76 | 76 |
77 private static int getFieldId(String assetType, String assetName) | 77 private static int getFieldId(Context context, String assetType, String asse
tName) |
78 throws ClassNotFoundException, NoSuchFieldException, IllegalAccessEx
ception { | 78 throws ClassNotFoundException, NoSuchFieldException, IllegalAccessExcept
ion { |
79 Class<?> clazz = null; | 79 Class<?> clazz = null; |
80 try { | 80 try { |
81 clazz = getClazz(assetType); | 81 clazz = getClazz(context, context.getPackageName(), 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 = ContextUtils.getApplicationContext().getPackage
Name(); | 85 String packageName = context.getPackageName(); |
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(assetType); | 91 clazz = getClazz(context, packageName, 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(int fieldId) { | 103 private static int getValueType(Context context, int fieldId) { |
104 TypedValue value = new TypedValue(); | 104 TypedValue value = new TypedValue(); |
105 ContextUtils.getApplicationContext().getResources().getValue(fieldId, va
lue, true); | 105 context.getResources().getValue(fieldId, value, true); |
106 return value.type; | 106 return value.type; |
107 } | 107 } |
108 | 108 |
109 private static InputStream openResource(Uri uri) { | 109 private static InputStream openResource(Context context, 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 int fieldId = getFieldId(assetType, assetName); | 130 // Use the application context for resolving the resource package na
me so that we do |
131 int valueType = getValueType(fieldId); | 131 // not use the browser's own resources. Note that if 'context' here
belongs to the |
| 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); |
132 if (valueType == TypedValue.TYPE_STRING) { | 139 if (valueType == TypedValue.TYPE_STRING) { |
133 return ContextUtils.getApplicationContext().getResources().openR
awResource(fieldId); | 140 return context.getResources().openRawResource(fieldId); |
134 } else { | 141 } else { |
135 Log.e(TAG, "Asset not of type string: " + uri); | 142 Log.e(TAG, "Asset not of type string: " + uri); |
136 return null; | 143 return null; |
137 } | 144 } |
138 } catch (ClassNotFoundException e) { | 145 } catch (ClassNotFoundException e) { |
139 Log.e(TAG, "Unable to open resource URL: " + uri, e); | 146 Log.e(TAG, "Unable to open resource URL: " + uri, e); |
140 return null; | 147 return null; |
141 } catch (NoSuchFieldException e) { | 148 } catch (NoSuchFieldException e) { |
142 Log.e(TAG, "Unable to open resource URL: " + uri, e); | 149 Log.e(TAG, "Unable to open resource URL: " + uri, e); |
143 return null; | 150 return null; |
144 } catch (IllegalAccessException e) { | 151 } catch (IllegalAccessException e) { |
145 Log.e(TAG, "Unable to open resource URL: " + uri, e); | 152 Log.e(TAG, "Unable to open resource URL: " + uri, e); |
146 return null; | 153 return null; |
147 } | 154 } |
148 } | 155 } |
149 | 156 |
150 private static InputStream openAsset(Uri uri) { | 157 private static InputStream openAsset(Context context, Uri uri) { |
151 assert uri.getScheme().equals(FILE_SCHEME); | 158 assert uri.getScheme().equals(FILE_SCHEME); |
152 assert uri.getPath() != null; | 159 assert uri.getPath() != null; |
153 assert uri.getPath().startsWith(nativeGetAndroidAssetPath()); | 160 assert uri.getPath().startsWith(nativeGetAndroidAssetPath()); |
154 String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), ""
); | 161 String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), ""
); |
155 try { | 162 try { |
156 AssetManager assets = ContextUtils.getApplicationContext().getAssets
(); | 163 AssetManager assets = context.getAssets(); |
157 return assets.open(path, AssetManager.ACCESS_STREAMING); | 164 return assets.open(path, AssetManager.ACCESS_STREAMING); |
158 } catch (IOException e) { | 165 } catch (IOException e) { |
159 Log.e(TAG, "Unable to open asset URL: " + uri); | 166 Log.e(TAG, "Unable to open asset URL: " + uri); |
160 return null; | 167 return null; |
161 } | 168 } |
162 } | 169 } |
163 | 170 |
164 private static InputStream openContent(Uri uri) { | 171 private static InputStream openContent(Context context, Uri uri) { |
165 assert uri.getScheme().equals(CONTENT_SCHEME); | 172 assert uri.getScheme().equals(CONTENT_SCHEME); |
166 try { | 173 try { |
167 return ContextUtils.getApplicationContext().getContentResolver().ope
nInputStream(uri); | 174 return context.getContentResolver().openInputStream(uri); |
168 } catch (Exception e) { | 175 } catch (Exception e) { |
169 Log.e(TAG, "Unable to open content URL: " + uri); | 176 Log.e(TAG, "Unable to open content URL: " + uri); |
170 return null; | 177 return null; |
171 } | 178 } |
172 } | 179 } |
173 | 180 |
174 /** | 181 /** |
175 * Determine the mime type for an Android resource. | 182 * Determine the mime type for an Android resource. |
176 * | 183 * @param context The context manager. |
177 * @param stream The opened input stream which to examine. | 184 * @param stream The opened input stream which to examine. |
178 * @param url The url from which the stream was opened. | 185 * @param url The url from which the stream was opened. |
179 * @return The mime type or null if the type is unknown. | 186 * @return The mime type or null if the type is unknown. |
180 */ | 187 */ |
181 @CalledByNative | 188 @CalledByNative |
182 public static String getMimeType(InputStream stream, String url) { | 189 public static String getMimeType(Context context, InputStream stream, String
url) { |
183 Uri uri = verifyUrl(url); | 190 Uri uri = verifyUrl(url); |
184 if (uri == null) { | 191 if (uri == null) { |
185 return null; | 192 return null; |
186 } | 193 } |
187 try { | 194 try { |
188 String path = uri.getPath(); | 195 String path = uri.getPath(); |
189 // The content URL type can be queried directly. | 196 // The content URL type can be queried directly. |
190 if (uri.getScheme().equals(CONTENT_SCHEME)) { | 197 if (uri.getScheme().equals(CONTENT_SCHEME)) { |
191 return ContextUtils.getApplicationContext().getContentResolver()
.getType(uri); | 198 return context.getContentResolver().getType(uri); |
192 // Asset files may have a known extension. | 199 // Asset files may have a known extension. |
193 } else if (uri.getScheme().equals(FILE_SCHEME) | 200 } else if (uri.getScheme().equals(FILE_SCHEME) |
194 && path.startsWith(nativeGetAndroidAssetPath())) { | 201 && path.startsWith(nativeGetAndroidAssetPath())) { |
195 String mimeType = URLConnection.guessContentTypeFromName(path); | 202 String mimeType = URLConnection.guessContentTypeFromName(path); |
196 if (mimeType != null) { | 203 if (mimeType != null) { |
197 return mimeType; | 204 return mimeType; |
198 } | 205 } |
199 } | 206 } |
200 } catch (Exception ex) { | 207 } catch (Exception ex) { |
201 Log.e(TAG, "Unable to get mime type" + url); | 208 Log.e(TAG, "Unable to get mime type" + url); |
202 return null; | 209 return null; |
203 } | 210 } |
204 // Fall back to sniffing the type from the stream. | 211 // Fall back to sniffing the type from the stream. |
205 try { | 212 try { |
206 return URLConnection.guessContentTypeFromStream(stream); | 213 return URLConnection.guessContentTypeFromStream(stream); |
207 } catch (IOException e) { | 214 } catch (IOException e) { |
208 return null; | 215 return null; |
209 } | 216 } |
210 } | 217 } |
211 | 218 |
212 /** | 219 /** |
213 * Make sure the given string URL is correctly formed and parse it into a Ur
i. | 220 * Make sure the given string URL is correctly formed and parse it into a Ur
i. |
214 * | |
215 * @return a Uri instance, or null if the URL was invalid. | 221 * @return a Uri instance, or null if the URL was invalid. |
216 */ | 222 */ |
217 private static Uri verifyUrl(String url) { | 223 private static Uri verifyUrl(String url) { |
218 if (url == null) { | 224 if (url == null) { |
219 return null; | 225 return null; |
220 } | 226 } |
221 Uri uri = Uri.parse(url); | 227 Uri uri = Uri.parse(url); |
222 if (uri == null) { | 228 if (uri == null) { |
223 Log.e(TAG, "Malformed URL: " + url); | 229 Log.e(TAG, "Malformed URL: " + url); |
224 return null; | 230 return null; |
225 } | 231 } |
226 String path = uri.getPath(); | 232 String path = uri.getPath(); |
227 if (path == null || path.length() == 0) { | 233 if (path == null || path.length() == 0) { |
228 Log.e(TAG, "URL does not have a path: " + url); | 234 Log.e(TAG, "URL does not have a path: " + url); |
229 return null; | 235 return null; |
230 } | 236 } |
231 return uri; | 237 return uri; |
232 } | 238 } |
233 | 239 |
| 240 /** |
| 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 |
| 249 private static native void nativeSetResourceContextForTesting(Context contex
t); |
234 private static native String nativeGetAndroidAssetPath(); | 250 private static native String nativeGetAndroidAssetPath(); |
235 | |
236 private static native String nativeGetAndroidResourcePath(); | 251 private static native String nativeGetAndroidResourcePath(); |
237 } | 252 } |
OLD | NEW |