Home | History | Annotate | Download | only in webkit
      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