blob: 257ed2a4011e7eecd74e843bd15cc9a017753351 [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;
21import android.util.Log;
22
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010023import java.util.Collection;
24import java.util.Map;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010025import java.util.HashMap;
Andrei Popescuaf9c77e2009-07-21 17:02:35 +010026import java.util.HashSet;
27import java.util.Iterator;
28import java.util.Set;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010029
Ben Murdoch7df19852009-04-22 13:07:58 +010030/**
31 * Functionality for manipulating the webstorage databases.
32 */
33public final class WebStorage {
34
35 /**
36 * Encapsulates a callback function to be executed when a new quota is made
37 * available. We primarily want this to allow us to call back the sleeping
38 * WebCore thread from outside the WebViewCore class (as the native call
39 * is private). It is imperative that this the setDatabaseQuota method is
40 * executed once a decision to either allow or deny new quota is made,
41 * otherwise the WebCore thread will remain asleep.
42 */
43 public interface QuotaUpdater {
44 public void updateQuota(long newQuota);
45 };
Nicolas Roard11e8fe52009-05-11 15:04:16 +010046
47 // Log tag
48 private static final String TAG = "webstorage";
49
50 // Global instance of a WebStorage
51 private static WebStorage sWebStorage;
52
Nicolas Roard11e8fe52009-05-11 15:04:16 +010053 // Message ids
54 static final int UPDATE = 0;
55 static final int SET_QUOTA_ORIGIN = 1;
56 static final int DELETE_ORIGIN = 2;
57 static final int DELETE_ALL = 3;
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010058 static final int GET_ORIGINS = 4;
59 static final int GET_USAGE_ORIGIN = 5;
60 static final int GET_QUOTA_ORIGIN = 6;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010061
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010062 // Message ids on the UI thread
63 static final int RETURN_ORIGINS = 0;
64 static final int RETURN_USAGE_ORIGIN = 1;
65 static final int RETURN_QUOTA_ORIGIN = 2;
66
67 private static final String ORIGINS = "origins";
68 private static final String ORIGIN = "origin";
69 private static final String CALLBACK = "callback";
70 private static final String USAGE = "usage";
71 private static final String QUOTA = "quota";
72
73 private Map <String, Origin> mOrigins;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010074
75 private Handler mHandler = null;
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010076 private Handler mUIHandler = null;
Nicolas Roard11e8fe52009-05-11 15:04:16 +010077
John Reck87745ce2010-11-30 14:00:54 -080078 public static class Origin {
79 private String mOrigin = null;
80 private long mQuota = 0;
81 private long mUsage = 0;
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010082
John Reck87745ce2010-11-30 14:00:54 -080083 private Origin(String origin, long quota, long usage) {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +010084 mOrigin = origin;
85 mQuota = quota;
86 mUsage = usage;
87 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +010088
John Reck87745ce2010-11-30 14:00:54 -080089 private Origin(String origin, long quota) {
Nicolas Roard11e8fe52009-05-11 15:04:16 +010090 mOrigin = origin;
91 mQuota = quota;
92 }
93
John Reck87745ce2010-11-30 14:00:54 -080094 private Origin(String origin) {
Nicolas Roard11e8fe52009-05-11 15:04:16 +010095 mOrigin = origin;
96 }
97
98 public String getOrigin() {
99 return mOrigin;
100 }
101
102 public long getQuota() {
103 return mQuota;
104 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100105
106 public long getUsage() {
107 return mUsage;
108 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100109 }
110
111 /**
112 * @hide
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100113 * Message handler, UI side
114 */
115 public void createUIHandler() {
116 if (mUIHandler == null) {
117 mUIHandler = new Handler() {
118 @Override
119 public void handleMessage(Message msg) {
120 switch (msg.what) {
121 case RETURN_ORIGINS: {
122 Map values = (Map) msg.obj;
123 Map origins = (Map) values.get(ORIGINS);
124 ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK);
125 callback.onReceiveValue(origins);
126 } break;
127
128 case RETURN_USAGE_ORIGIN: {
129 Map values = (Map) msg.obj;
130 ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
131 callback.onReceiveValue((Long)values.get(USAGE));
132 } break;
133
134 case RETURN_QUOTA_ORIGIN: {
135 Map values = (Map) msg.obj;
136 ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
137 callback.onReceiveValue((Long)values.get(QUOTA));
138 } break;
139 }
140 }
141 };
142 }
143 }
144
145 /**
146 * @hide
147 * Message handler, webcore side
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100148 */
Steve Blocke4b2d4d2010-02-19 12:21:35 +0000149 public synchronized void createHandler() {
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100150 if (mHandler == null) {
151 mHandler = new Handler() {
152 @Override
153 public void handleMessage(Message msg) {
154 switch (msg.what) {
155 case SET_QUOTA_ORIGIN: {
156 Origin website = (Origin) msg.obj;
157 nativeSetQuotaForOrigin(website.getOrigin(),
158 website.getQuota());
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100159 } break;
160
161 case DELETE_ORIGIN: {
162 Origin website = (Origin) msg.obj;
163 nativeDeleteOrigin(website.getOrigin());
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100164 } break;
165
166 case DELETE_ALL:
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100167 nativeDeleteAllData();
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100168 break;
169
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100170 case GET_ORIGINS: {
171 syncValues();
172 ValueCallback callback = (ValueCallback) msg.obj;
173 Map origins = new HashMap(mOrigins);
174 Map values = new HashMap<String, Object>();
175 values.put(CALLBACK, callback);
176 values.put(ORIGINS, origins);
177 postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
178 } break;
179
180 case GET_USAGE_ORIGIN: {
181 syncValues();
182 Map values = (Map) msg.obj;
183 String origin = (String) values.get(ORIGIN);
184 ValueCallback callback = (ValueCallback) values.get(CALLBACK);
185 Origin website = mOrigins.get(origin);
186 Map retValues = new HashMap<String, Object>();
187 retValues.put(CALLBACK, callback);
188 if (website != null) {
189 long usage = website.getUsage();
190 retValues.put(USAGE, new Long(usage));
191 }
192 postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues));
193 } break;
194
195 case GET_QUOTA_ORIGIN: {
196 syncValues();
197 Map values = (Map) msg.obj;
198 String origin = (String) values.get(ORIGIN);
199 ValueCallback callback = (ValueCallback) values.get(CALLBACK);
200 Origin website = mOrigins.get(origin);
201 Map retValues = new HashMap<String, Object>();
202 retValues.put(CALLBACK, callback);
203 if (website != null) {
204 long quota = website.getQuota();
205 retValues.put(QUOTA, new Long(quota));
206 }
207 postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues));
208 } break;
209
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100210 case UPDATE:
211 syncValues();
212 break;
213 }
214 }
215 };
216 }
217 }
218
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100219 /*
220 * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
221 * we need to get the values from webcore, but we cannot block while doing so
222 * as we used to do, as this could result in a full deadlock (other webcore
223 * messages received while we are still blocked here, see http://b/2127737).
224 *
225 * We have to do everything asynchronously, by providing a callback function.
226 * We post a message on the webcore thread (mHandler) that will get the result
227 * from webcore, and we post it back on the UI thread (using mUIHandler).
228 * We can then use the callback function to return the value.
229 */
230
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100231 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100232 * Returns a list of origins having a database
233 */
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100234 public void getOrigins(ValueCallback<Map> callback) {
235 if (callback != null) {
236 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
237 syncValues();
238 callback.onReceiveValue(mOrigins);
239 } else {
240 postMessage(Message.obtain(null, GET_ORIGINS, callback));
Andrei Popescu59e2ad92009-07-28 13:38:06 +0100241 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100242 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100243 }
244
245 /**
246 * Returns a list of origins having a database
247 * should only be called from WebViewCore.
248 */
249 Collection<Origin> getOriginsSync() {
250 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
251 update();
252 return mOrigins.values();
253 }
254 return null;
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100255 }
256
257 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100258 * Returns the use for a given origin
259 */
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100260 public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
261 if (callback == null) {
262 return;
263 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100264 if (origin == null) {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100265 callback.onReceiveValue(null);
266 return;
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100267 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100268 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
269 syncValues();
270 Origin website = mOrigins.get(origin);
271 callback.onReceiveValue(new Long(website.getUsage()));
272 } else {
273 HashMap values = new HashMap<String, Object>();
274 values.put(ORIGIN, origin);
275 values.put(CALLBACK, callback);
276 postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100277 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100278 }
279
280 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100281 * Returns the quota for a given origin
282 */
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100283 public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
284 if (callback == null) {
285 return;
286 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100287 if (origin == null) {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100288 callback.onReceiveValue(null);
289 return;
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100290 }
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100291 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
292 syncValues();
293 Origin website = mOrigins.get(origin);
294 callback.onReceiveValue(new Long(website.getUsage()));
295 } else {
296 HashMap values = new HashMap<String, Object>();
297 values.put(ORIGIN, origin);
298 values.put(CALLBACK, callback);
299 postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100300 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100301 }
302
303 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100304 * Set the quota for a given origin
305 */
306 public void setQuotaForOrigin(String origin, long quota) {
307 if (origin != null) {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100308 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
309 nativeSetQuotaForOrigin(origin, quota);
Ben Murdoch5647bb32009-06-18 17:48:34 +0100310 } else {
311 postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
312 new Origin(origin, quota)));
313 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100314 }
315 }
316
317 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100318 * Delete a given origin
319 */
320 public void deleteOrigin(String origin) {
321 if (origin != null) {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100322 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
323 nativeDeleteOrigin(origin);
Ben Murdoch5647bb32009-06-18 17:48:34 +0100324 } else {
325 postMessage(Message.obtain(null, DELETE_ORIGIN,
326 new Origin(origin)));
327 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100328 }
329 }
330
331 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100332 * Delete all databases
333 */
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100334 public void deleteAllData() {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100335 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100336 nativeDeleteAllData();
Ben Murdoch5647bb32009-06-18 17:48:34 +0100337 } else {
338 postMessage(Message.obtain(null, DELETE_ALL));
339 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100340 }
341
342 /**
Steve Block427efcc2010-03-19 15:26:42 +0000343 * Sets the maximum size of the ApplicationCache.
344 * This should only ever be called on the WebKit thread.
345 * @hide Pending API council approval
346 */
347 public void setAppCacheMaximumSize(long size) {
348 nativeSetAppCacheMaximumSize(size);
349 }
350
351 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100352 * Utility function to send a message to our handler
353 */
Steve Blocke4b2d4d2010-02-19 12:21:35 +0000354 private synchronized void postMessage(Message msg) {
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100355 if (mHandler != null) {
356 mHandler.sendMessage(msg);
357 }
358 }
359
360 /**
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100361 * Utility function to send a message to the handler on the UI thread
362 */
363 private void postUIMessage(Message msg) {
364 if (mUIHandler != null) {
365 mUIHandler.sendMessage(msg);
366 }
367 }
368
369 /**
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100370 * Get the global instance of WebStorage.
371 * @return A single instance of WebStorage.
372 */
373 public static WebStorage getInstance() {
374 if (sWebStorage == null) {
375 sWebStorage = new WebStorage();
376 }
377 return sWebStorage;
378 }
379
380 /**
381 * @hide
382 * Post a Sync request
383 */
384 public void update() {
Ben Murdoch5647bb32009-06-18 17:48:34 +0100385 if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
386 syncValues();
387 } else {
388 postMessage(Message.obtain(null, UPDATE));
389 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100390 }
391
392 /**
393 * Run on the webcore thread
Andrei Popescu9a56bb82009-07-22 19:21:48 +0100394 * set the local values with the current ones
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100395 */
396 private void syncValues() {
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100397 Set<String> tmp = nativeGetOrigins();
398 mOrigins = new HashMap<String, Origin>();
399 for (String origin : tmp) {
400 Origin website = new Origin(origin,
Andrei Popescu98409702010-01-08 16:41:23 +0000401 nativeGetQuotaForOrigin(origin),
402 nativeGetUsageForOrigin(origin));
Nicolas Roard6c24b4d2009-09-22 18:44:52 +0100403 mOrigins.put(origin, website);
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100404 }
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100405 }
406
407 // Native functions
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100408 private static native Set nativeGetOrigins();
Nicolas Roard11e8fe52009-05-11 15:04:16 +0100409 private static native long nativeGetUsageForOrigin(String origin);
410 private static native long nativeGetQuotaForOrigin(String origin);
411 private static native void nativeSetQuotaForOrigin(String origin, long quota);
412 private static native void nativeDeleteOrigin(String origin);
Andrei Popescuaf9c77e2009-07-21 17:02:35 +0100413 private static native void nativeDeleteAllData();
Steve Block427efcc2010-03-19 15:26:42 +0000414 private static native void nativeSetAppCacheMaximumSize(long size);
Ben Murdoch7df19852009-04-22 13:07:58 +0100415}