Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6c24b4d1 authored by Nicolas Roard's avatar Nicolas Roard
Browse files

Reimplement the settings to use async callbacks

parent 408cf852
Loading
Loading
Loading
Loading
+91 −56
Original line number Diff line number Diff line
@@ -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.
     */
@@ -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;
@@ -115,7 +161,6 @@ public final class GeolocationPermissions {
                }
            }
        }
        mLock.unlock();
    }

    /**
@@ -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
@@ -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));
            }
        }
    }

    /**
@@ -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));
        }
    }

    /**
@@ -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();
    }

    /**
+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);
};
+182 −81
Original line number Diff line number Diff line
@@ -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();

    /**
     * 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;
    }

    /**
@@ -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.
@@ -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
+9 −5
Original line number Diff line number Diff line
@@ -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;
    }