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 /** 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 86 private Origin(String origin, long quota, long usage) { 87 mOrigin = origin; 88 mQuota = quota; 89 mUsage = usage; 90 } 91 92 private Origin(String origin, long quota) { 93 mOrigin = origin; 94 mQuota = quota; 95 } 96 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 */ 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 */ 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 */ 125 public long getUsage() { 126 return mUsage; 127 } 128 } 129 130 /** 131 * @hide 132 * Message handler, UI side 133 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 367 public void setAppCacheMaximumSize(long size) { 368 nativeSetAppCacheMaximumSize(size); 369 } 370 371 /** 372 * Utility function to send a message to our handler 373 */ 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 */ 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 */ 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 */ 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 */ 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 428 private static native Set nativeGetOrigins(); 429 private static native long nativeGetUsageForOrigin(String origin); 430 private static native long nativeGetQuotaForOrigin(String origin); 431 private static native void nativeSetQuotaForOrigin(String origin, long quota); 432 private static native void nativeDeleteOrigin(String origin); 433 private static native void nativeDeleteAllData(); 434 private static native void nativeSetAppCacheMaximumSize(long size); 435 } 436