• 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     static class Origin {
79         String mOrigin = null;
80         long mQuota = 0;
81         long mUsage = 0;
82 
Origin(String origin, long quota, long usage)83         public Origin(String origin, long quota, long usage) {
84             mOrigin = origin;
85             mQuota = quota;
86             mUsage = usage;
87         }
88 
Origin(String origin, long quota)89         public Origin(String origin, long quota) {
90             mOrigin = origin;
91             mQuota = quota;
92         }
93 
Origin(String origin)94         public Origin(String origin) {
95             mOrigin = origin;
96         }
97 
getOrigin()98         public String getOrigin() {
99             return mOrigin;
100         }
101 
getQuota()102         public long getQuota() {
103             return mQuota;
104         }
105 
getUsage()106         public long getUsage() {
107             return mUsage;
108         }
109     }
110 
111     /**
112      * @hide
113      * Message handler, UI side
114      */
createUIHandler()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      */
createHandler()149     public 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      * @hide
234      */
getOrigins(ValueCallback<Map> callback)235     public void getOrigins(ValueCallback<Map> callback) {
236         if (callback != null) {
237             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
238                 syncValues();
239                 callback.onReceiveValue(mOrigins);
240             } else {
241                 postMessage(Message.obtain(null, GET_ORIGINS, callback));
242             }
243         }
244     }
245 
246     /**
247      * Returns a list of origins having a database
248      * should only be called from WebViewCore.
249      */
getOriginsSync()250     Collection<Origin> getOriginsSync() {
251         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
252             update();
253             return mOrigins.values();
254         }
255         return null;
256     }
257 
258     /**
259      * Returns the use for a given origin
260      * @hide
261      */
getUsageForOrigin(String origin, ValueCallback<Long> callback)262     public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
263         if (callback == null) {
264             return;
265         }
266         if (origin == null) {
267             callback.onReceiveValue(null);
268             return;
269         }
270         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
271             syncValues();
272             Origin website = mOrigins.get(origin);
273             callback.onReceiveValue(new Long(website.getUsage()));
274         } else {
275             HashMap values = new HashMap<String, Object>();
276             values.put(ORIGIN, origin);
277             values.put(CALLBACK, callback);
278             postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
279         }
280     }
281 
282     /**
283      * Returns the quota for a given origin
284      * @hide
285      */
getQuotaForOrigin(String origin, ValueCallback<Long> callback)286     public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
287         if (callback == null) {
288             return;
289         }
290         if (origin == null) {
291             callback.onReceiveValue(null);
292             return;
293         }
294         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
295             syncValues();
296             Origin website = mOrigins.get(origin);
297             callback.onReceiveValue(new Long(website.getUsage()));
298         } else {
299             HashMap values = new HashMap<String, Object>();
300             values.put(ORIGIN, origin);
301             values.put(CALLBACK, callback);
302             postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
303         }
304     }
305 
306     /**
307      * Set the quota for a given origin
308      * @hide
309      */
setQuotaForOrigin(String origin, long quota)310     public void setQuotaForOrigin(String origin, long quota) {
311         if (origin != null) {
312             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
313                 nativeSetQuotaForOrigin(origin, quota);
314             } else {
315                 postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
316                     new Origin(origin, quota)));
317             }
318         }
319     }
320 
321     /**
322      * Delete a given origin
323      * @hide
324      */
deleteOrigin(String origin)325     public void deleteOrigin(String origin) {
326         if (origin != null) {
327             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
328                 nativeDeleteOrigin(origin);
329             } else {
330                 postMessage(Message.obtain(null, DELETE_ORIGIN,
331                     new Origin(origin)));
332             }
333         }
334     }
335 
336     /**
337      * Delete all databases
338      * @hide
339      */
deleteAllData()340     public void deleteAllData() {
341         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
342             nativeDeleteAllData();
343         } else {
344             postMessage(Message.obtain(null, DELETE_ALL));
345         }
346     }
347 
348     /**
349      * Utility function to send a message to our handler
350      */
postMessage(Message msg)351     private void postMessage(Message msg) {
352         if (mHandler != null) {
353             mHandler.sendMessage(msg);
354         }
355     }
356 
357     /**
358      * Utility function to send a message to the handler on the UI thread
359      */
postUIMessage(Message msg)360     private void postUIMessage(Message msg) {
361         if (mUIHandler != null) {
362             mUIHandler.sendMessage(msg);
363         }
364     }
365 
366     /**
367      * Get the global instance of WebStorage.
368      * @return A single instance of WebStorage.
369      * @hide
370      */
getInstance()371     public static WebStorage getInstance() {
372       if (sWebStorage == null) {
373           sWebStorage = new WebStorage();
374       }
375       return sWebStorage;
376     }
377 
378     /**
379      * @hide
380      * Post a Sync request
381      */
update()382     public void update() {
383         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
384             syncValues();
385         } else {
386             postMessage(Message.obtain(null, UPDATE));
387         }
388     }
389 
390     /**
391      * Run on the webcore thread
392      * set the local values with the current ones
393      */
syncValues()394     private void syncValues() {
395         Set<String> tmp = nativeGetOrigins();
396         mOrigins = new HashMap<String, Origin>();
397         for (String origin : tmp) {
398             Origin website = new Origin(origin,
399                                  nativeGetUsageForOrigin(origin),
400                                  nativeGetQuotaForOrigin(origin));
401             mOrigins.put(origin, website);
402         }
403     }
404 
405     // Native functions
nativeGetOrigins()406     private static native Set nativeGetOrigins();
nativeGetUsageForOrigin(String origin)407     private static native long nativeGetUsageForOrigin(String origin);
nativeGetQuotaForOrigin(String origin)408     private static native long nativeGetQuotaForOrigin(String origin);
nativeSetQuotaForOrigin(String origin, long quota)409     private static native void nativeSetQuotaForOrigin(String origin, long quota);
nativeDeleteOrigin(String origin)410     private static native void nativeDeleteOrigin(String origin);
nativeDeleteAllData()411     private static native void nativeDeleteAllData();
412 }
413