blob: aaf414697de78a23e80b4d1eaba3b6be98a6aaa0 [file] [log] [blame]
Ben Murdoch7df19852009-04-22 13:07:58 +01001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
Nicolas Roard11e8fe52009-05-11 15:04:16 +010019import android.os.Handler;
20import android.os.Message;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010021
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010022import java.util.Collection;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010023import java.util.HashMap;
Jonathan Dixon36196b62012-03-28 16:40:37 +010024import java.util.Map;
Andrei Popescuaf9c77e2009-07-21 17:02:35 +010025import java.util.Set;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010026
Ben Murdoch7df19852009-04-22 13:07:58 +010027/**
28 * Functionality for manipulating the webstorage databases.
29 */
Jonathan Dixon939e5042012-04-12 21:21:07 +010030public class WebStorage {
Ben Murdoch7df19852009-04-22 13:07:58 +010031
32 /**
33 * Encapsulates a callback function to be executed when a new quota is made
34 * available. We primarily want this to allow us to call back the sleeping
35 * WebCore thread from outside the WebViewCore class (as the native call
36 * is private). It is imperative that this the setDatabaseQuota method is
37 * executed once a decision to either allow or deny new quota is made,
38 * otherwise the WebCore thread will remain asleep.
39 */
40 public interface QuotaUpdater {
41 public void updateQuota(long newQuota);
42 };
Nicolas Roard11e8fe52009-05-11 15:04:16 +010043
Nicolas Roard11e8fe52009-05-11 15:04:16 +010044 // Global instance of a WebStorage
45 private static WebStorage sWebStorage;
46
Nicolas Roard11e8fe52009-05-11 15:04:16 +010047 // Message ids
48 static final int UPDATE = 0;
49 static final int SET_QUOTA_ORIGIN = 1;
50 static final int DELETE_ORIGIN = 2;
51 static final int DELETE_ALL = 3;
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010052 static final int GET_ORIGINS = 4;
53 static final int GET_USAGE_ORIGIN = 5;
54 static final int GET_QUOTA_ORIGIN = 6;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010055
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010056 // Message ids on the UI thread
57 static final int RETURN_ORIGINS = 0;
58 static final int RETURN_USAGE_ORIGIN = 1;
59 static final int RETURN_QUOTA_ORIGIN = 2;
60
61 private static final String ORIGINS = "origins";
62 private static final String ORIGIN = "origin";
63 private static final String CALLBACK = "callback";
64 private static final String USAGE = "usage";
65 private static final String QUOTA = "quota";
66
67 private Map <String, Origin> mOrigins;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010068
69 private Handler mHandler = null;
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010070 private Handler mUIHandler = null;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010071
John Reck9b2a59b2011-01-24 17:31:39 -080072 /**
73 * Class containing the HTML5 database quota and usage for an origin.
74 */
John Reck87745ce2010-11-30 14:00:54 -080075 public static class Origin {
76 private String mOrigin = null;
77 private long mQuota = 0;
78 private long mUsage = 0;
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010079
John Reck87745ce2010-11-30 14:00:54 -080080 private Origin(String origin, long quota, long usage) {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010081 mOrigin = origin;
82 mQuota = quota;
83 mUsage = usage;
84 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +010085
John Reck87745ce2010-11-30 14:00:54 -080086 private Origin(String origin, long quota) {
Nicolas Roard11e8fe52009-05-11 15:04:16 +010087 mOrigin = origin;
88 mQuota = quota;
89 }
90
John Reck87745ce2010-11-30 14:00:54 -080091 private Origin(String origin) {
Nicolas Roard11e8fe52009-05-11 15:04:16 +010092 mOrigin = origin;
93 }
94
John Reck9b2a59b2011-01-24 17:31:39 -080095 /**
96 * An origin string is created using WebCore::SecurityOrigin::toString().
97 * Note that WebCore::SecurityOrigin uses 0 (which is not printed) for
98 * the port if the port is the default for the protocol. Eg
99 * http://www.google.com and http://www.google.com:80 both record a port
100 * of 0 and hence toString() == 'http://www.google.com' for both.
101 * @return The origin string.
102 */
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100103 public String getOrigin() {
104 return mOrigin;
105 }
106
John Reck9b2a59b2011-01-24 17:31:39 -0800107 /**
108 * Returns the quota for this origin's HTML5 database.
109 * @return The quota in bytes.
110 */
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100111 public long getQuota() {
112 return mQuota;
113 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100114
John Reck9b2a59b2011-01-24 17:31:39 -0800115 /**
116 * Returns the usage for this origin's HTML5 database.
117 * @return The usage in bytes.
118 */
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100119 public long getUsage() {
120 return mUsage;
121 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100122 }
123
124 /**
125 * @hide
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100126 * Message handler, UI side
127 */
128 public void createUIHandler() {
129 if (mUIHandler == null) {
130 mUIHandler = new Handler() {
131 @Override
132 public void handleMessage(Message msg) {
133 switch (msg.what) {
134 case RETURN_ORIGINS: {
135 Map values = (Map) msg.obj;
136 Map origins = (Map) values.get(ORIGINS);
137 ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK);
138 callback.onReceiveValue(origins);
139 } break;
140
141 case RETURN_USAGE_ORIGIN: {
142 Map values = (Map) msg.obj;
143 ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
144 callback.onReceiveValue((Long)values.get(USAGE));
145 } break;
146
147 case RETURN_QUOTA_ORIGIN: {
148 Map values = (Map) msg.obj;
149 ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
150 callback.onReceiveValue((Long)values.get(QUOTA));
151 } break;
152 }
153 }
154 };
155 }
156 }
157
158 /**
159 * @hide
160 * Message handler, webcore side
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100161 */
Steve Blocke4b2d4d2010-02-19 12:21:35 +0000162 public synchronized void createHandler() {
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100163 if (mHandler == null) {
164 mHandler = new Handler() {
165 @Override
166 public void handleMessage(Message msg) {
167 switch (msg.what) {
168 case SET_QUOTA_ORIGIN: {
169 Origin website = (Origin) msg.obj;
170 nativeSetQuotaForOrigin(website.getOrigin(),
171 website.getQuota());
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100172 } break;
173
174 case DELETE_ORIGIN: {
175 Origin website = (Origin) msg.obj;
176 nativeDeleteOrigin(website.getOrigin());
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100177 } break;
178
179 case DELETE_ALL:
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100180 nativeDeleteAllData();
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100181 break;
182
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100183 case GET_ORIGINS: {
184 syncValues();
185 ValueCallback callback = (ValueCallback) msg.obj;
186 Map origins = new HashMap(mOrigins);
187 Map values = new HashMap<String, Object>();
188 values.put(CALLBACK, callback);
189 values.put(ORIGINS, origins);
190 postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
191 } break;
192
193 case GET_USAGE_ORIGIN: {
194 syncValues();
195 Map values = (Map) msg.obj;
196 String origin = (String) values.get(ORIGIN);
197 ValueCallback callback = (ValueCallback) values.get(CALLBACK);
198 Origin website = mOrigins.get(origin);
199 Map retValues = new HashMap<String, Object>();
200 retValues.put(CALLBACK, callback);
201 if (website != null) {
202 long usage = website.getUsage();
203 retValues.put(USAGE, new Long(usage));
204 }
205 postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues));
206 } break;
207
208 case GET_QUOTA_ORIGIN: {
209 syncValues();
210 Map values = (Map) msg.obj;
211 String origin = (String) values.get(ORIGIN);
212 ValueCallback callback = (ValueCallback) values.get(CALLBACK);
213 Origin website = mOrigins.get(origin);
214 Map retValues = new HashMap<String, Object>();
215 retValues.put(CALLBACK, callback);
216 if (website != null) {
217 long quota = website.getQuota();
218 retValues.put(QUOTA, new Long(quota));
219 }
220 postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues));
221 } break;
222
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100223 case UPDATE:
224 syncValues();
225 break;
226 }
227 }
228 };
229 }
230 }
231
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100232 /*
233 * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
234 * we need to get the values from webcore, but we cannot block while doing so
235 * as we used to do, as this could result in a full deadlock (other webcore
236 * messages received while we are still blocked here, see http://b/2127737).
237 *
238 * We have to do everything asynchronously, by providing a callback function.
239 * We post a message on the webcore thread (mHandler) that will get the result
240 * from webcore, and we post it back on the UI thread (using mUIHandler).
241 * We can then use the callback function to return the value.
242 */
243
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100244 /**
John Reck9b2a59b2011-01-24 17:31:39 -0800245 * Returns a list of origins having a database. The Map is of type
246 * Map<String, Origin>.
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100247 */
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100248 public void getOrigins(ValueCallback<Map> callback) {
249 if (callback != null) {
250 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
251 syncValues();
252 callback.onReceiveValue(mOrigins);
253 } else {
254 postMessage(Message.obtain(null, GET_ORIGINS, callback));
Andrei Popescu59e2ad92009-07-28 13:38:06 +0100255 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100256 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100257 }
258
259 /**
260 * Returns a list of origins having a database
261 * should only be called from WebViewCore.
262 */
263 Collection<Origin> getOriginsSync() {
264 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
265 update();
266 return mOrigins.values();
267 }
268 return null;
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100269 }
270
271 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100272 * Returns the use for a given origin
273 */
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100274 public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
275 if (callback == null) {
276 return;
277 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100278 if (origin == null) {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100279 callback.onReceiveValue(null);
280 return;
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100281 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100282 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
283 syncValues();
284 Origin website = mOrigins.get(origin);
285 callback.onReceiveValue(new Long(website.getUsage()));
286 } else {
287 HashMap values = new HashMap<String, Object>();
288 values.put(ORIGIN, origin);
289 values.put(CALLBACK, callback);
290 postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100291 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100292 }
293
294 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100295 * Returns the quota for a given origin
296 */
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100297 public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
298 if (callback == null) {
299 return;
300 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100301 if (origin == null) {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100302 callback.onReceiveValue(null);
303 return;
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100304 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100305 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
306 syncValues();
307 Origin website = mOrigins.get(origin);
308 callback.onReceiveValue(new Long(website.getUsage()));
309 } else {
310 HashMap values = new HashMap<String, Object>();
311 values.put(ORIGIN, origin);
312 values.put(CALLBACK, callback);
313 postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100314 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100315 }
316
317 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100318 * Set the quota for a given origin
319 */
320 public void setQuotaForOrigin(String origin, long quota) {
321 if (origin != null) {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100322 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
323 nativeSetQuotaForOrigin(origin, quota);
Ben Murdoch5647bb32009-06-18 17:48:34 +0100324 } else {
325 postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
326 new Origin(origin, quota)));
327 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100328 }
329 }
330
331 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100332 * Delete a given origin
333 */
334 public void deleteOrigin(String origin) {
335 if (origin != null) {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100336 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
337 nativeDeleteOrigin(origin);
Ben Murdoch5647bb32009-06-18 17:48:34 +0100338 } else {
339 postMessage(Message.obtain(null, DELETE_ORIGIN,
340 new Origin(origin)));
341 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100342 }
343 }
344
345 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100346 * Delete all databases
347 */
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100348 public void deleteAllData() {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100349 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100350 nativeDeleteAllData();
Ben Murdoch5647bb32009-06-18 17:48:34 +0100351 } else {
352 postMessage(Message.obtain(null, DELETE_ALL));
353 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100354 }
355
356 /**
Steve Block427efcc2010-03-19 15:26:42 +0000357 * Sets the maximum size of the ApplicationCache.
358 * This should only ever be called on the WebKit thread.
Jonathan Dixon4cde13a2011-12-16 18:36:20 +0000359 * @hide
Steve Block427efcc2010-03-19 15:26:42 +0000360 */
361 public void setAppCacheMaximumSize(long size) {
362 nativeSetAppCacheMaximumSize(size);
363 }
364
365 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100366 * Utility function to send a message to our handler
367 */
Steve Blocke4b2d4d2010-02-19 12:21:35 +0000368 private synchronized void postMessage(Message msg) {
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100369 if (mHandler != null) {
370 mHandler.sendMessage(msg);
371 }
372 }
373
374 /**
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100375 * Utility function to send a message to the handler on the UI thread
376 */
377 private void postUIMessage(Message msg) {
378 if (mUIHandler != null) {
379 mUIHandler.sendMessage(msg);
380 }
381 }
382
383 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100384 * Get the global instance of WebStorage.
385 * @return A single instance of WebStorage.
386 */
387 public static WebStorage getInstance() {
388 if (sWebStorage == null) {
389 sWebStorage = new WebStorage();
390 }
391 return sWebStorage;
392 }
393
394 /**
395 * @hide
396 * Post a Sync request
397 */
398 public void update() {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100399 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
400 syncValues();
401 } else {
402 postMessage(Message.obtain(null, UPDATE));
403 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100404 }
405
406 /**
407 * Run on the webcore thread
Andrei Popescu9a56bb82009-07-22 19:21:48 +0100408 * set the local values with the current ones
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100409 */
410 private void syncValues() {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100411 Set<String> tmp = nativeGetOrigins();
412 mOrigins = new HashMap<String, Origin>();
413 for (String origin : tmp) {
414 Origin website = new Origin(origin,
Andrei Popescu98409702010-01-08 16:41:23 +0000415 nativeGetQuotaForOrigin(origin),
416 nativeGetUsageForOrigin(origin));
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100417 mOrigins.put(origin, website);
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100418 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100419 }
420
Jonathan Dixona3dc86e2012-03-28 12:27:36 +0100421 /**
422 * This class should not be instantiated directly, applications must only use
423 * {@link #getInstance()} to obtain the instance.
424 * Note this constructor was erroneously public and published in SDK levels prior to 16, but
425 * applications using it would receive a non-functional instance of this class (there was no
426 * way to call createHandler() and createUIHandler(), so it would not work).
427 * @hide
428 */
429 public WebStorage() {}
430
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100431 // Native functions
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100432 private static native Set nativeGetOrigins();
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100433 private static native long nativeGetUsageForOrigin(String origin);
434 private static native long nativeGetQuotaForOrigin(String origin);
435 private static native void nativeSetQuotaForOrigin(String origin, long quota);
436 private static native void nativeDeleteOrigin(String origin);
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100437 private static native void nativeDeleteAllData();
Steve Block427efcc2010-03-19 15:26:42 +0000438 private static native void nativeSetAppCacheMaximumSize(long size);
Ben Murdoch7df19852009-04-22 13:07:58 +0100439}