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 { 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 83 public Origin(String origin, long quota, long usage) { 84 mOrigin = origin; 85 mQuota = quota; 86 mUsage = usage; 87 } 88 89 public Origin(String origin, long quota) { 90 mOrigin = origin; 91 mQuota = quota; 92 } 93 94 public 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