1/*
2 * Copyright (C) 2012 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;
21
22import java.util.Collection;
23import java.util.HashMap;
24import java.util.Map;
25import java.util.Set;
26
27/** @hide */
28public class WebStorageClassic extends WebStorage {
29    // Global instance of a WebStorage
30    private static WebStorageClassic sWebStorage;
31
32    // Message ids
33    static final int UPDATE = 0;
34    static final int SET_QUOTA_ORIGIN = 1;
35    static final int DELETE_ORIGIN = 2;
36    static final int DELETE_ALL = 3;
37    static final int GET_ORIGINS = 4;
38    static final int GET_USAGE_ORIGIN = 5;
39    static final int GET_QUOTA_ORIGIN = 6;
40
41    // Message ids on the UI thread
42    static final int RETURN_ORIGINS = 0;
43    static final int RETURN_USAGE_ORIGIN = 1;
44    static final int RETURN_QUOTA_ORIGIN = 2;
45
46    private static final String ORIGINS = "origins";
47    private static final String ORIGIN = "origin";
48    private static final String CALLBACK = "callback";
49    private static final String USAGE = "usage";
50    private static final String QUOTA = "quota";
51
52    private Map <String, Origin> mOrigins;
53
54    private Handler mHandler = null;
55    private Handler mUIHandler = null;
56
57    /**
58     * @hide
59     * Message handler, UI side
60     * @hide
61     */
62    public void createUIHandler() {
63        if (mUIHandler == null) {
64            mUIHandler = new Handler() {
65                @Override
66                public void handleMessage(Message msg) {
67                    switch (msg.what) {
68                        case RETURN_ORIGINS: {
69                            Map values = (Map) msg.obj;
70                            Map origins = (Map) values.get(ORIGINS);
71                            ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK);
72                            callback.onReceiveValue(origins);
73                            } break;
74
75                        case RETURN_USAGE_ORIGIN: {
76                            Map values = (Map) msg.obj;
77                            ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
78                            callback.onReceiveValue((Long)values.get(USAGE));
79                            } break;
80
81                        case RETURN_QUOTA_ORIGIN: {
82                            Map values = (Map) msg.obj;
83                            ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
84                            callback.onReceiveValue((Long)values.get(QUOTA));
85                            } break;
86                    }
87                }
88            };
89        }
90    }
91
92    /**
93     * Message handler, WebCore side
94     * @hide
95     */
96    public synchronized void createHandler() {
97        if (mHandler == null) {
98            mHandler = new Handler() {
99                @Override
100                public void handleMessage(Message msg) {
101                    switch (msg.what) {
102                        case SET_QUOTA_ORIGIN: {
103                            Origin website = (Origin) msg.obj;
104                            nativeSetQuotaForOrigin(website.getOrigin(),
105                                                    website.getQuota());
106                            } break;
107
108                        case DELETE_ORIGIN: {
109                            Origin website = (Origin) msg.obj;
110                            nativeDeleteOrigin(website.getOrigin());
111                            } break;
112
113                        case DELETE_ALL:
114                            nativeDeleteAllData();
115                            break;
116
117                        case GET_ORIGINS: {
118                            syncValues();
119                            ValueCallback callback = (ValueCallback) msg.obj;
120                            Map origins = new HashMap(mOrigins);
121                            Map values = new HashMap<String, Object>();
122                            values.put(CALLBACK, callback);
123                            values.put(ORIGINS, origins);
124                            postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
125                            } break;
126
127                        case GET_USAGE_ORIGIN: {
128                            syncValues();
129                            Map values = (Map) msg.obj;
130                            String origin = (String) values.get(ORIGIN);
131                            ValueCallback callback = (ValueCallback) values.get(CALLBACK);
132                            Origin website = mOrigins.get(origin);
133                            Map retValues = new HashMap<String, Object>();
134                            retValues.put(CALLBACK, callback);
135                            if (website != null) {
136                                long usage = website.getUsage();
137                                retValues.put(USAGE, new Long(usage));
138                            }
139                            postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues));
140                            } break;
141
142                        case GET_QUOTA_ORIGIN: {
143                            syncValues();
144                            Map values = (Map) msg.obj;
145                            String origin = (String) values.get(ORIGIN);
146                            ValueCallback callback = (ValueCallback) values.get(CALLBACK);
147                            Origin website = mOrigins.get(origin);
148                            Map retValues = new HashMap<String, Object>();
149                            retValues.put(CALLBACK, callback);
150                            if (website != null) {
151                                long quota = website.getQuota();
152                                retValues.put(QUOTA, new Long(quota));
153                            }
154                            postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues));
155                            } break;
156
157                        case UPDATE:
158                            syncValues();
159                            break;
160                    }
161                }
162            };
163        }
164    }
165
166    /*
167     * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
168     * we need to get the values from WebCore, but we cannot block while doing so
169     * as we used to do, as this could result in a full deadlock (other WebCore
170     * messages received while we are still blocked here, see http://b/2127737).
171     *
172     * We have to do everything asynchronously, by providing a callback function.
173     * We post a message on the WebCore thread (mHandler) that will get the result
174     * from WebCore, and we post it back on the UI thread (using mUIHandler).
175     * We can then use the callback function to return the value.
176     */
177
178    @Override
179    public void getOrigins(ValueCallback<Map> callback) {
180        if (callback != null) {
181            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
182                syncValues();
183                callback.onReceiveValue(mOrigins);
184            } else {
185                postMessage(Message.obtain(null, GET_ORIGINS, callback));
186            }
187        }
188    }
189
190    /**
191     * Returns a list of origins having a database
192     * should only be called from WebViewCore.
193     */
194    Collection<Origin> getOriginsSync() {
195        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
196            update();
197            return mOrigins.values();
198        }
199        return null;
200    }
201
202    @Override
203    public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
204        if (callback == null) {
205            return;
206        }
207        if (origin == null) {
208            callback.onReceiveValue(null);
209            return;
210        }
211        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
212            syncValues();
213            Origin website = mOrigins.get(origin);
214            callback.onReceiveValue(new Long(website.getUsage()));
215        } else {
216            HashMap values = new HashMap<String, Object>();
217            values.put(ORIGIN, origin);
218            values.put(CALLBACK, callback);
219            postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
220        }
221    }
222
223    @Override
224    public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
225        if (callback == null) {
226            return;
227        }
228        if (origin == null) {
229            callback.onReceiveValue(null);
230            return;
231        }
232        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
233            syncValues();
234            Origin website = mOrigins.get(origin);
235            callback.onReceiveValue(new Long(website.getUsage()));
236        } else {
237            HashMap values = new HashMap<String, Object>();
238            values.put(ORIGIN, origin);
239            values.put(CALLBACK, callback);
240            postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
241        }
242    }
243
244    @Override
245    public void setQuotaForOrigin(String origin, long quota) {
246        if (origin != null) {
247            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
248                nativeSetQuotaForOrigin(origin, quota);
249            } else {
250                postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
251                    new Origin(origin, quota)));
252            }
253        }
254    }
255
256    @Override
257    public void deleteOrigin(String origin) {
258        if (origin != null) {
259            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
260                nativeDeleteOrigin(origin);
261            } else {
262                postMessage(Message.obtain(null, DELETE_ORIGIN,
263                    new Origin(origin)));
264            }
265        }
266    }
267
268    @Override
269    public void deleteAllData() {
270        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
271            nativeDeleteAllData();
272        } else {
273            postMessage(Message.obtain(null, DELETE_ALL));
274        }
275    }
276
277    /**
278     * Sets the maximum size of the ApplicationCache.
279     * This should only ever be called on the WebKit thread.
280     * Not part of the base-class API: this is only used by dump render tree.
281     */
282    public void setAppCacheMaximumSize(long size) {
283        nativeSetAppCacheMaximumSize(size);
284    }
285
286    /**
287     * Utility function to send a message to our handler
288     */
289    private synchronized void postMessage(Message msg) {
290        if (mHandler != null) {
291            mHandler.sendMessage(msg);
292        }
293    }
294
295    /**
296     * Utility function to send a message to the handler on the UI thread
297     */
298    private void postUIMessage(Message msg) {
299        if (mUIHandler != null) {
300            mUIHandler.sendMessage(msg);
301        }
302    }
303
304    /**
305     * Get the singleton instance of this class.
306     * @return The singleton {@link WebStorage} instance.
307     */
308    public static WebStorageClassic getInstance() {
309      if (sWebStorage == null) {
310          sWebStorage = new WebStorageClassic();
311      }
312      return sWebStorage;
313    }
314
315    /**
316     * @hide
317     * Post a Sync request
318     */
319    public void update() {
320        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
321            syncValues();
322        } else {
323            postMessage(Message.obtain(null, UPDATE));
324        }
325    }
326
327    /**
328     * Run on the WebCore thread
329     * set the local values with the current ones
330     */
331    private void syncValues() {
332        Set<String> tmp = nativeGetOrigins();
333        mOrigins = new HashMap<String, Origin>();
334        for (String origin : tmp) {
335            Origin website = new Origin(origin,
336                                 nativeGetQuotaForOrigin(origin),
337                                 nativeGetUsageForOrigin(origin));
338            mOrigins.put(origin, website);
339        }
340    }
341
342    WebStorageClassic() {}
343
344    // Native functions
345    private static native Set nativeGetOrigins();
346    private static native long nativeGetUsageForOrigin(String origin);
347    private static native long nativeGetQuotaForOrigin(String origin);
348    private static native void nativeSetQuotaForOrigin(String origin, long quota);
349    private static native void nativeDeleteOrigin(String origin);
350    private static native void nativeDeleteAllData();
351    private static native void nativeSetAppCacheMaximumSize(long size);
352}
353