diff options
-rwxr-xr-x | core/java/android/webkit/GeolocationPermissions.java | 147 | ||||
-rw-r--r-- | core/java/android/webkit/ValueCallback.java | 29 | ||||
-rw-r--r-- | core/java/android/webkit/WebStorage.java | 263 | ||||
-rw-r--r-- | core/java/android/webkit/WebViewCore.java | 14 |
4 files changed, 311 insertions, 142 deletions
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java index e985ccc..c0cac01 100755 --- a/core/java/android/webkit/GeolocationPermissions.java +++ b/core/java/android/webkit/GeolocationPermissions.java @@ -19,10 +19,9 @@ package android.webkit; import android.os.Handler; import android.os.Message; import android.util.Log; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; @@ -47,15 +46,13 @@ public final class GeolocationPermissions { private static GeolocationPermissions sInstance; private Handler mHandler; + private Handler mUIHandler; // Members used to transfer the origins and permissions between threads. private Set<String> mOrigins; private boolean mAllowed; private Set<String> mOriginsToClear; private Set<String> mOriginsToAllow; - private static Lock mLock = new ReentrantLock(); - private static boolean mUpdated; - private static Condition mUpdatedCondition = mLock.newCondition(); // Message ids static final int GET_ORIGINS = 0; @@ -64,6 +61,15 @@ public final class GeolocationPermissions { static final int ALLOW = 3; static final int CLEAR_ALL = 4; + // Message ids on the UI thread + static final int RETURN_ORIGINS = 0; + static final int RETURN_ALLOWED = 1; + + private static final String ORIGINS = "origins"; + private static final String ORIGIN = "origin"; + private static final String CALLBACK = "callback"; + private static final String ALLOWED = "allowed"; + /** * Gets the singleton instance of the class. */ @@ -75,22 +81,62 @@ public final class GeolocationPermissions { } /** + * Creates the UI message handler. Must be called on the UI thread. + */ + public void createUIHandler() { + if (mUIHandler == null) { + mUIHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + // Runs on the UI thread. + switch (msg.what) { + case RETURN_ORIGINS: { + Map values = (Map) msg.obj; + Set origins = (Set) values.get(ORIGINS); + ValueCallback<Set> callback = (ValueCallback<Set>) values.get(CALLBACK); + callback.onReceiveValue(origins); + } break; + case RETURN_ALLOWED: { + Map values = (Map) msg.obj; + Boolean allowed = (Boolean) values.get(ALLOWED); + ValueCallback<Boolean> callback = (ValueCallback<Boolean>) values.get(CALLBACK); + callback.onReceiveValue(allowed); + } break; + } + } + }; + } + } + + /** * Creates the message handler. Must be called on the WebKit thread. */ public void createHandler() { - mLock.lock(); if (mHandler == null) { mHandler = new Handler() { @Override public void handleMessage(Message msg) { // Runs on the WebKit thread. switch (msg.what) { - case GET_ORIGINS: + case GET_ORIGINS: { getOriginsImpl(); - break; - case GET_ALLOWED: - getAllowedImpl((String) msg.obj); - break; + ValueCallback callback = (ValueCallback) msg.obj; + Set origins = new HashSet(mOrigins); + Map values = new HashMap<String, Object>(); + values.put(CALLBACK, callback); + values.put(ORIGINS, origins); + postUIMessage(Message.obtain(null, RETURN_ORIGINS, values)); + } break; + case GET_ALLOWED: { + Map values = (Map) msg.obj; + String origin = (String) values.get(ORIGIN); + ValueCallback callback = (ValueCallback) values.get(CALLBACK); + getAllowedImpl(origin); + Map retValues = new HashMap<String, Object>(); + retValues.put(CALLBACK, callback); + retValues.put(ALLOWED, new Boolean(mAllowed)); + postUIMessage(Message.obtain(null, RETURN_ALLOWED, retValues)); + } break; case CLEAR: nativeClear((String) msg.obj); break; @@ -115,7 +161,6 @@ public final class GeolocationPermissions { } } } - mLock.unlock(); } /** @@ -127,29 +172,31 @@ public final class GeolocationPermissions { } /** + * Utility function to send a message to the handler on the UI thread + */ + private void postUIMessage(Message msg) { + if (mUIHandler != null) { + mUIHandler.sendMessage(msg); + } + } + + /** * Gets the set of origins for which Geolocation permissions are stored. * Note that we represent the origins as strings. These are created using * WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules' * (Database, Geolocation etc) do so, it's safe to match up origins for the * purposes of displaying UI. */ - public Set getOrigins() { - // Called on the UI thread. - Set origins = null; - mLock.lock(); - try { - mUpdated = false; - postMessage(Message.obtain(null, GET_ORIGINS)); - while (!mUpdated) { - mUpdatedCondition.await(); + public void getOrigins(ValueCallback<Set> callback) { + if (callback != null) { + if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) { + getOriginsImpl(); + Set origins = new HashSet(mOrigins); + callback.onReceiveValue(origins); + } else { + postMessage(Message.obtain(null, GET_ORIGINS, callback)); } - origins = mOrigins; - } catch (InterruptedException e) { - Log.e(TAG, "Exception while waiting for update", e); - } finally { - mLock.unlock(); } - return origins; } /** @@ -157,33 +204,29 @@ public final class GeolocationPermissions { */ private void getOriginsImpl() { // Called on the WebKit thread. - mLock.lock(); mOrigins = nativeGetOrigins(); - mUpdated = true; - mUpdatedCondition.signal(); - mLock.unlock(); } /** * Gets the permission state for the specified origin. */ - public boolean getAllowed(String origin) { - // Called on the UI thread. - boolean allowed = false; - mLock.lock(); - try { - mUpdated = false; - postMessage(Message.obtain(null, GET_ALLOWED, origin)); - while (!mUpdated) { - mUpdatedCondition.await(); - } - allowed = mAllowed; - } catch (InterruptedException e) { - Log.e(TAG, "Exception while waiting for update", e); - } finally { - mLock.unlock(); + public void getAllowed(String origin, ValueCallback<Boolean> callback) { + if (callback == null) { + return; + } + if (origin == null) { + callback.onReceiveValue(null); + return; + } + if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) { + getAllowedImpl(origin); + callback.onReceiveValue(new Boolean(mAllowed)); + } else { + Map values = new HashMap<String, Object>(); + values.put(ORIGIN, origin); + values.put(CALLBACK, callback); + postMessage(Message.obtain(null, GET_ALLOWED, values)); } - return allowed; } /** @@ -191,11 +234,7 @@ public final class GeolocationPermissions { */ private void getAllowedImpl(String origin) { // Called on the WebKit thread. - mLock.lock(); mAllowed = nativeGetAllowed(origin); - mUpdated = true; - mUpdatedCondition.signal(); - mLock.unlock(); } /** @@ -205,7 +244,6 @@ public final class GeolocationPermissions { */ public void clear(String origin) { // Called on the UI thread. - mLock.lock(); if (mHandler == null) { if (mOriginsToClear == null) { mOriginsToClear = new HashSet<String>(); @@ -217,7 +255,6 @@ public final class GeolocationPermissions { } else { postMessage(Message.obtain(null, CLEAR, origin)); } - mLock.unlock(); } /** @@ -227,7 +264,6 @@ public final class GeolocationPermissions { */ public void allow(String origin) { // Called on the UI thread. - mLock.lock(); if (mHandler == null) { if (mOriginsToAllow == null) { mOriginsToAllow = new HashSet<String>(); @@ -239,7 +275,6 @@ public final class GeolocationPermissions { } else { postMessage(Message.obtain(null, ALLOW, origin)); } - mLock.unlock(); } /** diff --git a/core/java/android/webkit/ValueCallback.java b/core/java/android/webkit/ValueCallback.java new file mode 100644 index 0000000..d8c5cdc --- /dev/null +++ b/core/java/android/webkit/ValueCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +/** + * A callback interface used to returns values asynchronously + * + * @hide pending council approval + */ +public interface ValueCallback<T> { + /** + * Invoked when we have the result + */ + public void onReceiveValue(T value); +}; diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java index ae560fb..0022248 100644 --- a/core/java/android/webkit/WebStorage.java +++ b/core/java/android/webkit/WebStorage.java @@ -20,9 +20,8 @@ import android.os.Handler; import android.os.Message; import android.util.Log; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.Collection; +import java.util.Map; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -51,28 +50,41 @@ public final class WebStorage { // Global instance of a WebStorage private static WebStorage sWebStorage; - // We keep the origins, quotas and usages as member values - // that we protect via a lock and update in syncValues(). - // This is needed to transfer this data across threads. - private static Lock mLock = new ReentrantLock(); - private static Condition mUpdateCondition = mLock.newCondition(); - private static boolean mUpdateAvailable; - // Message ids static final int UPDATE = 0; static final int SET_QUOTA_ORIGIN = 1; static final int DELETE_ORIGIN = 2; static final int DELETE_ALL = 3; + static final int GET_ORIGINS = 4; + static final int GET_USAGE_ORIGIN = 5; + static final int GET_QUOTA_ORIGIN = 6; + + // Message ids on the UI thread + static final int RETURN_ORIGINS = 0; + static final int RETURN_USAGE_ORIGIN = 1; + static final int RETURN_QUOTA_ORIGIN = 2; + + private static final String ORIGINS = "origins"; + private static final String ORIGIN = "origin"; + private static final String CALLBACK = "callback"; + private static final String USAGE = "usage"; + private static final String QUOTA = "quota"; - private Set <String> mOrigins; - private HashMap <String, Long> mQuotas = new HashMap<String, Long>(); - private HashMap <String, Long> mUsages = new HashMap<String, Long>(); + private Map <String, Origin> mOrigins; private Handler mHandler = null; + private Handler mUIHandler = null; - private static class Origin { + static class Origin { String mOrigin = null; long mQuota = 0; + long mUsage = 0; + + public Origin(String origin, long quota, long usage) { + mOrigin = origin; + mQuota = quota; + mUsage = usage; + } public Origin(String origin, long quota) { mOrigin = origin; @@ -90,11 +102,49 @@ public final class WebStorage { public long getQuota() { return mQuota; } + + public long getUsage() { + return mUsage; + } + } + + /** + * @hide + * Message handler, UI side + */ + public void createUIHandler() { + if (mUIHandler == null) { + mUIHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case RETURN_ORIGINS: { + Map values = (Map) msg.obj; + Map origins = (Map) values.get(ORIGINS); + ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK); + callback.onReceiveValue(origins); + } break; + + case RETURN_USAGE_ORIGIN: { + Map values = (Map) msg.obj; + ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK); + callback.onReceiveValue((Long)values.get(USAGE)); + } break; + + case RETURN_QUOTA_ORIGIN: { + Map values = (Map) msg.obj; + ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK); + callback.onReceiveValue((Long)values.get(QUOTA)); + } break; + } + } + }; + } } /** * @hide - * Message handler + * Message handler, webcore side */ public void createHandler() { if (mHandler == null) { @@ -117,6 +167,46 @@ public final class WebStorage { nativeDeleteAllData(); break; + case GET_ORIGINS: { + syncValues(); + ValueCallback callback = (ValueCallback) msg.obj; + Map origins = new HashMap(mOrigins); + Map values = new HashMap<String, Object>(); + values.put(CALLBACK, callback); + values.put(ORIGINS, origins); + postUIMessage(Message.obtain(null, RETURN_ORIGINS, values)); + } break; + + case GET_USAGE_ORIGIN: { + syncValues(); + Map values = (Map) msg.obj; + String origin = (String) values.get(ORIGIN); + ValueCallback callback = (ValueCallback) values.get(CALLBACK); + Origin website = mOrigins.get(origin); + Map retValues = new HashMap<String, Object>(); + retValues.put(CALLBACK, callback); + if (website != null) { + long usage = website.getUsage(); + retValues.put(USAGE, new Long(usage)); + } + postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues)); + } break; + + case GET_QUOTA_ORIGIN: { + syncValues(); + Map values = (Map) msg.obj; + String origin = (String) values.get(ORIGIN); + ValueCallback callback = (ValueCallback) values.get(CALLBACK); + Origin website = mOrigins.get(origin); + Map retValues = new HashMap<String, Object>(); + retValues.put(CALLBACK, callback); + if (website != null) { + long quota = website.getQuota(); + retValues.put(QUOTA, new Long(quota)); + } + postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues)); + } break; + case UPDATE: syncValues(); break; @@ -126,82 +216,91 @@ public final class WebStorage { } } + /* + * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(), + * we need to get the values from webcore, but we cannot block while doing so + * as we used to do, as this could result in a full deadlock (other webcore + * messages received while we are still blocked here, see http://b/2127737). + * + * We have to do everything asynchronously, by providing a callback function. + * We post a message on the webcore thread (mHandler) that will get the result + * from webcore, and we post it back on the UI thread (using mUIHandler). + * We can then use the callback function to return the value. + */ + /** * @hide * Returns a list of origins having a database */ - public Set getOrigins() { - Set ret = null; - mLock.lock(); - try { - mUpdateAvailable = false; - update(); - while (!mUpdateAvailable) { - mUpdateCondition.await(); + public void getOrigins(ValueCallback<Map> callback) { + if (callback != null) { + if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) { + syncValues(); + callback.onReceiveValue(mOrigins); + } else { + postMessage(Message.obtain(null, GET_ORIGINS, callback)); } - ret = mOrigins; - } catch (InterruptedException e) { - Log.e(TAG, "Exception while waiting on the updated origins", e); - } finally { - mLock.unlock(); } - return ret; + } + + /** + * Returns a list of origins having a database + * should only be called from WebViewCore. + */ + Collection<Origin> getOriginsSync() { + if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) { + update(); + return mOrigins.values(); + } + return null; } /** * @hide * Returns the use for a given origin */ - public long getUsageForOrigin(String origin) { - long ret = 0; + public void getUsageForOrigin(String origin, ValueCallback<Long> callback) { + if (callback == null) { + return; + } if (origin == null) { - return ret; + callback.onReceiveValue(null); + return; } - mLock.lock(); - try { - mUpdateAvailable = false; - update(); - while (!mUpdateAvailable) { - mUpdateCondition.await(); - } - Long usage = mUsages.get(origin); - if (usage != null) { - ret = usage.longValue(); - } - } catch (InterruptedException e) { - Log.e(TAG, "Exception while waiting on the updated origins", e); - } finally { - mLock.unlock(); + if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) { + syncValues(); + Origin website = mOrigins.get(origin); + callback.onReceiveValue(new Long(website.getUsage())); + } else { + HashMap values = new HashMap<String, Object>(); + values.put(ORIGIN, origin); + values.put(CALLBACK, callback); + postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values)); } - return ret; } /** * @hide * Returns the quota for a given origin */ - public long getQuotaForOrigin(String origin) { - long ret = 0; + public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) { + if (callback == null) { + return; + } if (origin == null) { - return ret; + callback.onReceiveValue(null); + return; } - mLock.lock(); - try { - mUpdateAvailable = false; - update(); - while (!mUpdateAvailable) { - mUpdateCondition.await(); - } - Long quota = mQuotas.get(origin); - if (quota != null) { - ret = quota.longValue(); - } - } catch (InterruptedException e) { - Log.e(TAG, "Exception while waiting on the updated origins", e); - } finally { - mLock.unlock(); + if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) { + syncValues(); + Origin website = mOrigins.get(origin); + callback.onReceiveValue(new Long(website.getUsage())); + } else { + HashMap values = new HashMap<String, Object>(); + values.put(ORIGIN, origin); + values.put(CALLBACK, callback); + postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values)); } - return ret; } /** @@ -256,6 +355,15 @@ public final class WebStorage { } /** + * Utility function to send a message to the handler on the UI thread + */ + private void postUIMessage(Message msg) { + if (mUIHandler != null) { + mUIHandler.sendMessage(msg); + } + } + + /** * @hide * Get the global instance of WebStorage. * @return A single instance of WebStorage. @@ -284,21 +392,14 @@ public final class WebStorage { * set the local values with the current ones */ private void syncValues() { - mLock.lock(); - Set tmp = nativeGetOrigins(); - mOrigins = new HashSet<String>(); - mQuotas.clear(); - mUsages.clear(); - Iterator<String> iter = tmp.iterator(); - while (iter.hasNext()) { - String origin = iter.next(); - mOrigins.add(origin); - mQuotas.put(origin, new Long(nativeGetQuotaForOrigin(origin))); - mUsages.put(origin, new Long(nativeGetUsageForOrigin(origin))); + Set<String> tmp = nativeGetOrigins(); + mOrigins = new HashMap<String, Origin>(); + for (String origin : tmp) { + Origin website = new Origin(origin, + nativeGetUsageForOrigin(origin), + nativeGetQuotaForOrigin(origin)); + mOrigins.put(origin, website); } - mUpdateAvailable = true; - mUpdateCondition.signal(); - mLock.unlock(); } // Native functions diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index e734444..e7654cc 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -37,6 +37,7 @@ import android.view.SurfaceView; import android.view.View; import java.util.ArrayList; +import java.util.Collection; import java.util.Map; import java.util.Set; @@ -162,8 +163,10 @@ final class WebViewCore { // The WebIconDatabase needs to be initialized within the UI thread so // just request the instance here. WebIconDatabase.getInstance(); - // Create the WebStorage singleton - WebStorage.getInstance(); + // Create the WebStorage singleton and the UI handler + WebStorage.getInstance().createUIHandler(); + // Create the UI handler for GeolocationPermissions + GeolocationPermissions.getInstance().createUIHandler(); // Send a message to initialize the WebViewCore. Message init = sWebCoreHandler.obtainMessage( WebCoreThread.INITIALIZE, this); @@ -1519,13 +1522,14 @@ final class WebViewCore { // callbacks. Computes the sum of database quota for all origins. private long getUsedQuota() { WebStorage webStorage = WebStorage.getInstance(); - Set<String> origins = webStorage.getOrigins(); + Collection<WebStorage.Origin> origins = webStorage.getOriginsSync(); + if (origins == null) { return 0; } long usedQuota = 0; - for (String origin : origins) { - usedQuota += webStorage.getQuotaForOrigin(origin); + for (WebStorage.Origin website : origins) { + usedQuota += website.getQuota(); } return usedQuota; } |