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