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 |