• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.webkit;
18 
19 import android.os.Handler;
20 import android.os.Message;
21 import android.util.Log;
22 
23 import java.util.Collection;
24 import java.util.Map;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.Set;
29 
30 /**
31  * Functionality for manipulating the webstorage databases.
32  */
33 public 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 {
updateQuota(long newQuota)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     /**
79      * Class containing the HTML5 database quota and usage for an origin.
80      */
81     public static class Origin {
82         private String mOrigin = null;
83         private long mQuota = 0;
84         private long mUsage = 0;
85 
Origin(String origin, long quota, long usage)86         private Origin(String origin, long quota, long usage) {
87             mOrigin = origin;
88             mQuota = quota;
89             mUsage = usage;
90         }
91 
Origin(String origin, long quota)92         private Origin(String origin, long quota) {
93             mOrigin = origin;
94             mQuota = quota;
95         }
96 
Origin(String origin)97         private Origin(String origin) {
98             mOrigin = origin;
99         }
100 
101         /**
102          * An origin string is created using WebCore::SecurityOrigin::toString().
103          * Note that WebCore::SecurityOrigin uses 0 (which is not printed) for
104          * the port if the port is the default for the protocol. Eg
105          * http://www.google.com and http://www.google.com:80 both record a port
106          * of 0 and hence toString() == 'http://www.google.com' for both.
107          * @return The origin string.
108          */
getOrigin()109         public String getOrigin() {
110             return mOrigin;
111         }
112 
113         /**
114          * Returns the quota for this origin's HTML5 database.
115          * @return The quota in bytes.
116          */
getQuota()117         public long getQuota() {
118             return mQuota;
119         }
120 
121         /**
122          * Returns the usage for this origin's HTML5 database.
123          * @return The usage in bytes.
124          */
getUsage()125         public long getUsage() {
126             return mUsage;
127         }
128     }
129 
130     /**
131      * @hide
132      * Message handler, UI side
133      */
createUIHandler()134     public void createUIHandler() {
135         if (mUIHandler == null) {
136             mUIHandler = new Handler() {
137                 @Override
138                 public void handleMessage(Message msg) {
139                     switch (msg.what) {
140                         case RETURN_ORIGINS: {
141                             Map values = (Map) msg.obj;
142                             Map origins = (Map) values.get(ORIGINS);
143                             ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK);
144                             callback.onReceiveValue(origins);
145                             } break;
146 
147                         case RETURN_USAGE_ORIGIN: {
148                             Map values = (Map) msg.obj;
149                             ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
150                             callback.onReceiveValue((Long)values.get(USAGE));
151                             } break;
152 
153                         case RETURN_QUOTA_ORIGIN: {
154                             Map values = (Map) msg.obj;
155                             ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
156                             callback.onReceiveValue((Long)values.get(QUOTA));
157                             } break;
158                     }
159                 }
160             };
161         }
162     }
163 
164     /**
165      * @hide
166      * Message handler, webcore side
167      */
createHandler()168     public synchronized void createHandler() {
169         if (mHandler == null) {
170             mHandler = new Handler() {
171                 @Override
172                 public void handleMessage(Message msg) {
173                     switch (msg.what) {
174                         case SET_QUOTA_ORIGIN: {
175                             Origin website = (Origin) msg.obj;
176                             nativeSetQuotaForOrigin(website.getOrigin(),
177                                                     website.getQuota());
178                             } break;
179 
180                         case DELETE_ORIGIN: {
181                             Origin website = (Origin) msg.obj;
182                             nativeDeleteOrigin(website.getOrigin());
183                             } break;
184 
185                         case DELETE_ALL:
186                             nativeDeleteAllData();
187                             break;
188 
189                         case GET_ORIGINS: {
190                             syncValues();
191                             ValueCallback callback = (ValueCallback) msg.obj;
192                             Map origins = new HashMap(mOrigins);
193                             Map values = new HashMap<String, Object>();
194                             values.put(CALLBACK, callback);
195                             values.put(ORIGINS, origins);
196                             postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
197                             } break;
198 
199                         case GET_USAGE_ORIGIN: {
200                             syncValues();
201                             Map values = (Map) msg.obj;
202                             String origin = (String) values.get(ORIGIN);
203                             ValueCallback callback = (ValueCallback) values.get(CALLBACK);
204                             Origin website = mOrigins.get(origin);
205                             Map retValues = new HashMap<String, Object>();
206                             retValues.put(CALLBACK, callback);
207                             if (website != null) {
208                                 long usage = website.getUsage();
209                                 retValues.put(USAGE, new Long(usage));
210                             }
211                             postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues));
212                             } break;
213 
214                         case GET_QUOTA_ORIGIN: {
215                             syncValues();
216                             Map values = (Map) msg.obj;
217                             String origin = (String) values.get(ORIGIN);
218                             ValueCallback callback = (ValueCallback) values.get(CALLBACK);
219                             Origin website = mOrigins.get(origin);
220                             Map retValues = new HashMap<String, Object>();
221                             retValues.put(CALLBACK, callback);
222                             if (website != null) {
223                                 long quota = website.getQuota();
224                                 retValues.put(QUOTA, new Long(quota));
225                             }
226                             postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues));
227                             } break;
228 
229                         case UPDATE:
230                             syncValues();
231                             break;
232                     }
233                 }
234             };
235         }
236     }
237 
238     /*
239      * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
240      * we need to get the values from webcore, but we cannot block while doing so
241      * as we used to do, as this could result in a full deadlock (other webcore
242      * messages received while we are still blocked here, see http://b/2127737).
243      *
244      * We have to do everything asynchronously, by providing a callback function.
245      * We post a message on the webcore thread (mHandler) that will get the result
246      * from webcore, and we post it back on the UI thread (using mUIHandler).
247      * We can then use the callback function to return the value.
248      */
249 
250     /**
251      * Returns a list of origins having a database. The Map is of type
252      * Map<String, Origin>.
253      */
getOrigins(ValueCallback<Map> callback)254     public void getOrigins(ValueCallback<Map> callback) {
255         if (callback != null) {
256             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
257                 syncValues();
258                 callback.onReceiveValue(mOrigins);
259             } else {
260                 postMessage(Message.obtain(null, GET_ORIGINS, callback));
261             }
262         }
263     }
264 
265     /**
266      * Returns a list of origins having a database
267      * should only be called from WebViewCore.
268      */
getOriginsSync()269     Collection<Origin> getOriginsSync() {
270         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
271             update();
272             return mOrigins.values();
273         }
274         return null;
275     }
276 
277     /**
278      * Returns the use for a given origin
279      */
getUsageForOrigin(String origin, ValueCallback<Long> callback)280     public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
281         if (callback == null) {
282             return;
283         }
284         if (origin == null) {
285             callback.onReceiveValue(null);
286             return;
287         }
288         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
289             syncValues();
290             Origin website = mOrigins.get(origin);
291             callback.onReceiveValue(new Long(website.getUsage()));
292         } else {
293             HashMap values = new HashMap<String, Object>();
294             values.put(ORIGIN, origin);
295             values.put(CALLBACK, callback);
296             postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
297         }
298     }
299 
300     /**
301      * Returns the quota for a given origin
302      */
getQuotaForOrigin(String origin, ValueCallback<Long> callback)303     public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
304         if (callback == null) {
305             return;
306         }
307         if (origin == null) {
308             callback.onReceiveValue(null);
309             return;
310         }
311         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
312             syncValues();
313             Origin website = mOrigins.get(origin);
314             callback.onReceiveValue(new Long(website.getUsage()));
315         } else {
316             HashMap values = new HashMap<String, Object>();
317             values.put(ORIGIN, origin);
318             values.put(CALLBACK, callback);
319             postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
320         }
321     }
322 
323     /**
324      * Set the quota for a given origin
325      */
setQuotaForOrigin(String origin, long quota)326     public void setQuotaForOrigin(String origin, long quota) {
327         if (origin != null) {
328             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
329                 nativeSetQuotaForOrigin(origin, quota);
330             } else {
331                 postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
332                     new Origin(origin, quota)));
333             }
334         }
335     }
336 
337     /**
338      * Delete a given origin
339      */
deleteOrigin(String origin)340     public void deleteOrigin(String origin) {
341         if (origin != null) {
342             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
343                 nativeDeleteOrigin(origin);
344             } else {
345                 postMessage(Message.obtain(null, DELETE_ORIGIN,
346                     new Origin(origin)));
347             }
348         }
349     }
350 
351     /**
352      * Delete all databases
353      */
deleteAllData()354     public void deleteAllData() {
355         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
356             nativeDeleteAllData();
357         } else {
358             postMessage(Message.obtain(null, DELETE_ALL));
359         }
360     }
361 
362     /**
363      * Sets the maximum size of the ApplicationCache.
364      * This should only ever be called on the WebKit thread.
365      * @hide Pending API council approval
366      */
setAppCacheMaximumSize(long size)367     public void setAppCacheMaximumSize(long size) {
368         nativeSetAppCacheMaximumSize(size);
369     }
370 
371     /**
372      * Utility function to send a message to our handler
373      */
postMessage(Message msg)374     private synchronized void postMessage(Message msg) {
375         if (mHandler != null) {
376             mHandler.sendMessage(msg);
377         }
378     }
379 
380     /**
381      * Utility function to send a message to the handler on the UI thread
382      */
postUIMessage(Message msg)383     private void postUIMessage(Message msg) {
384         if (mUIHandler != null) {
385             mUIHandler.sendMessage(msg);
386         }
387     }
388 
389     /**
390      * Get the global instance of WebStorage.
391      * @return A single instance of WebStorage.
392      */
getInstance()393     public static WebStorage getInstance() {
394       if (sWebStorage == null) {
395           sWebStorage = new WebStorage();
396       }
397       return sWebStorage;
398     }
399 
400     /**
401      * @hide
402      * Post a Sync request
403      */
update()404     public void update() {
405         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
406             syncValues();
407         } else {
408             postMessage(Message.obtain(null, UPDATE));
409         }
410     }
411 
412     /**
413      * Run on the webcore thread
414      * set the local values with the current ones
415      */
syncValues()416     private void syncValues() {
417         Set<String> tmp = nativeGetOrigins();
418         mOrigins = new HashMap<String, Origin>();
419         for (String origin : tmp) {
420             Origin website = new Origin(origin,
421                                  nativeGetQuotaForOrigin(origin),
422                                  nativeGetUsageForOrigin(origin));
423             mOrigins.put(origin, website);
424         }
425     }
426 
427     // Native functions
nativeGetOrigins()428     private static native Set nativeGetOrigins();
nativeGetUsageForOrigin(String origin)429     private static native long nativeGetUsageForOrigin(String origin);
nativeGetQuotaForOrigin(String origin)430     private static native long nativeGetQuotaForOrigin(String origin);
nativeSetQuotaForOrigin(String origin, long quota)431     private static native void nativeSetQuotaForOrigin(String origin, long quota);
nativeDeleteOrigin(String origin)432     private static native void nativeDeleteOrigin(String origin);
nativeDeleteAllData()433     private static native void nativeDeleteAllData();
nativeSetAppCacheMaximumSize(long size)434     private static native void nativeSetAppCacheMaximumSize(long size);
435 }
436