WebStorage.java revision 87745ce21fe3f65b8cf7a92372c24227821318d3
1/*
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
19import android.os.Handler;
20import android.os.Message;
21import android.util.Log;
22
23import java.util.Collection;
24import java.util.Map;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.Set;
29
30/**
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    };
46
47    // Log tag
48    private static final String TAG = "webstorage";
49
50    // Global instance of a WebStorage
51    private static WebStorage sWebStorage;
52
53    // 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;
58    static final int GET_ORIGINS = 4;
59    static final int GET_USAGE_ORIGIN = 5;
60    static final int GET_QUOTA_ORIGIN = 6;
61
62    // 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;
74
75    private Handler mHandler = null;
76    private Handler mUIHandler = null;
77
78    public static class Origin {
79        private String mOrigin = null;
80        private long mQuota = 0;
81        private long mUsage = 0;
82
83        private Origin(String origin, long quota, long usage) {
84            mOrigin = origin;
85            mQuota = quota;
86            mUsage = usage;
87        }
88
89        private Origin(String origin, long quota) {
90            mOrigin = origin;
91            mQuota = quota;
92        }
93
94        private Origin(String origin) {
95            mOrigin = origin;
96        }
97
98        public String getOrigin() {
99            return mOrigin;
100        }
101
102        public long getQuota() {
103            return mQuota;
104        }
105
106        public long getUsage() {
107            return mUsage;
108        }
109    }
110
111    /**
112     * @hide
113     * 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
148     */
149    public synchronized void createHandler() {
150        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());
159                            } break;
160
161                        case DELETE_ORIGIN: {
162                            Origin website = (Origin) msg.obj;
163                            nativeDeleteOrigin(website.getOrigin());
164                            } break;
165
166                        case DELETE_ALL:
167                            nativeDeleteAllData();
168                            break;
169
170                        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
210                        case UPDATE:
211                            syncValues();
212                            break;
213                    }
214                }
215            };
216        }
217    }
218
219    /*
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
231    /**
232     * Returns a list of origins having a database
233     */
234    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));
241            }
242        }
243    }
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;
255    }
256
257    /**
258     * Returns the use for a given origin
259     */
260    public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
261        if (callback == null) {
262            return;
263        }
264        if (origin == null) {
265            callback.onReceiveValue(null);
266            return;
267        }
268        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));
277        }
278    }
279
280    /**
281     * Returns the quota for a given origin
282     */
283    public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
284        if (callback == null) {
285            return;
286        }
287        if (origin == null) {
288            callback.onReceiveValue(null);
289            return;
290        }
291        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));
300        }
301    }
302
303    /**
304     * Set the quota for a given origin
305     */
306    public void setQuotaForOrigin(String origin, long quota) {
307        if (origin != null) {
308            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
309                nativeSetQuotaForOrigin(origin, quota);
310            } else {
311                postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
312                    new Origin(origin, quota)));
313            }
314        }
315    }
316
317    /**
318     * Delete a given origin
319     */
320    public void deleteOrigin(String origin) {
321        if (origin != null) {
322            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
323                nativeDeleteOrigin(origin);
324            } else {
325                postMessage(Message.obtain(null, DELETE_ORIGIN,
326                    new Origin(origin)));
327            }
328        }
329    }
330
331    /**
332     * Delete all databases
333     */
334    public void deleteAllData() {
335        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
336            nativeDeleteAllData();
337        } else {
338            postMessage(Message.obtain(null, DELETE_ALL));
339        }
340    }
341
342    /**
343     * 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    /**
352     * Utility function to send a message to our handler
353     */
354    private synchronized void postMessage(Message msg) {
355        if (mHandler != null) {
356            mHandler.sendMessage(msg);
357        }
358    }
359
360    /**
361     * 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    /**
370     * 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() {
385        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
386            syncValues();
387        } else {
388            postMessage(Message.obtain(null, UPDATE));
389        }
390    }
391
392    /**
393     * Run on the webcore thread
394     * set the local values with the current ones
395     */
396    private void syncValues() {
397        Set<String> tmp = nativeGetOrigins();
398        mOrigins = new HashMap<String, Origin>();
399        for (String origin : tmp) {
400            Origin website = new Origin(origin,
401                                 nativeGetQuotaForOrigin(origin),
402                                 nativeGetUsageForOrigin(origin));
403            mOrigins.put(origin, website);
404        }
405    }
406
407    // Native functions
408    private static native Set nativeGetOrigins();
409    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);
413    private static native void nativeDeleteAllData();
414    private static native void nativeSetAppCacheMaximumSize(long size);
415}
416