Loading core/java/android/webkit/GeolocationPermissions.java +91 −56 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. */ Loading @@ -74,23 +80,63 @@ public final class GeolocationPermissions { return sInstance; } /** * 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; Loading @@ -115,7 +161,6 @@ public final class GeolocationPermissions { } } } mLock.unlock(); } /** Loading @@ -126,6 +171,15 @@ public final class GeolocationPermissions { mHandler.sendMessage(msg); } /** * 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 Loading @@ -133,23 +187,16 @@ public final class GeolocationPermissions { * (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(); } origins = mOrigins; } catch (InterruptedException e) { Log.e(TAG, "Exception while waiting for update", e); } finally { mLock.unlock(); } return origins; 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)); } } } /** Loading @@ -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(); } return allowed; 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)); } } /** Loading @@ -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(); } /** Loading @@ -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>(); Loading @@ -217,7 +255,6 @@ public final class GeolocationPermissions { } else { postMessage(Message.obtain(null, CLEAR, origin)); } mLock.unlock(); } /** Loading @@ -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>(); Loading @@ -239,7 +275,6 @@ public final class GeolocationPermissions { } else { postMessage(Message.obtain(null, ALLOW, origin)); } mLock.unlock(); } /** Loading core/java/android/webkit/ValueCallback.java 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * 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); }; core/java/android/webkit/WebStorage.java +182 −81 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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) { Loading @@ -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; Loading @@ -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(); /** * 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 ret; return null; } /** * @hide * Returns the use for a given origin */ public long getUsageForOrigin(String origin) { long ret = 0; if (origin == null) { return ret; } mLock.lock(); try { mUpdateAvailable = false; update(); while (!mUpdateAvailable) { mUpdateCondition.await(); public void getUsageForOrigin(String origin, ValueCallback<Long> callback) { if (callback == null) { return; } Long usage = mUsages.get(origin); if (usage != null) { ret = usage.longValue(); if (origin == null) { callback.onReceiveValue(null); return; } } 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; if (origin == null) { return ret; public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) { if (callback == null) { return; } mLock.lock(); try { mUpdateAvailable = false; update(); while (!mUpdateAvailable) { mUpdateCondition.await(); } Long quota = mQuotas.get(origin); if (quota != null) { ret = quota.longValue(); if (origin == null) { callback.onReceiveValue(null); return; } } 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; } /** Loading Loading @@ -255,6 +354,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. Loading Loading @@ -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))); } mUpdateAvailable = true; mUpdateCondition.signal(); mLock.unlock(); 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); } } // Native functions Loading core/java/android/webkit/WebViewCore.java +9 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; } Loading Loading
core/java/android/webkit/GeolocationPermissions.java +91 −56 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. */ Loading @@ -74,23 +80,63 @@ public final class GeolocationPermissions { return sInstance; } /** * 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; Loading @@ -115,7 +161,6 @@ public final class GeolocationPermissions { } } } mLock.unlock(); } /** Loading @@ -126,6 +171,15 @@ public final class GeolocationPermissions { mHandler.sendMessage(msg); } /** * 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 Loading @@ -133,23 +187,16 @@ public final class GeolocationPermissions { * (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(); } origins = mOrigins; } catch (InterruptedException e) { Log.e(TAG, "Exception while waiting for update", e); } finally { mLock.unlock(); } return origins; 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)); } } } /** Loading @@ -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(); } return allowed; 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)); } } /** Loading @@ -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(); } /** Loading @@ -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>(); Loading @@ -217,7 +255,6 @@ public final class GeolocationPermissions { } else { postMessage(Message.obtain(null, CLEAR, origin)); } mLock.unlock(); } /** Loading @@ -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>(); Loading @@ -239,7 +275,6 @@ public final class GeolocationPermissions { } else { postMessage(Message.obtain(null, ALLOW, origin)); } mLock.unlock(); } /** Loading
core/java/android/webkit/ValueCallback.java 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * 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); };
core/java/android/webkit/WebStorage.java +182 −81 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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) { Loading @@ -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; Loading @@ -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(); /** * 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 ret; return null; } /** * @hide * Returns the use for a given origin */ public long getUsageForOrigin(String origin) { long ret = 0; if (origin == null) { return ret; } mLock.lock(); try { mUpdateAvailable = false; update(); while (!mUpdateAvailable) { mUpdateCondition.await(); public void getUsageForOrigin(String origin, ValueCallback<Long> callback) { if (callback == null) { return; } Long usage = mUsages.get(origin); if (usage != null) { ret = usage.longValue(); if (origin == null) { callback.onReceiveValue(null); return; } } 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; if (origin == null) { return ret; public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) { if (callback == null) { return; } mLock.lock(); try { mUpdateAvailable = false; update(); while (!mUpdateAvailable) { mUpdateCondition.await(); } Long quota = mQuotas.get(origin); if (quota != null) { ret = quota.longValue(); if (origin == null) { callback.onReceiveValue(null); return; } } 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; } /** Loading Loading @@ -255,6 +354,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. Loading Loading @@ -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))); } mUpdateAvailable = true; mUpdateCondition.signal(); mLock.unlock(); 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); } } // Native functions Loading
core/java/android/webkit/WebViewCore.java +9 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; } Loading