GeolocationPermissions.java revision e4b2d4dc7db426052d1dfebc40f6b64a001b6d73
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
17package android.webkit;
18
19import android.os.Handler;
20import android.os.Message;
21import android.util.Log;
22import java.util.HashMap;
23import java.util.HashSet;
24import java.util.Map;
25import java.util.Set;
26
27
28/**
29 * This class is used to get Geolocation permissions from, and set them on the
30 * WebView. For example, it could be used to allow a user to manage Geolocation
31 * permissions from a browser's UI.
32 *
33 * Permissions are managed on a per-origin basis, as required by the
34 * Geolocation spec - http://dev.w3.org/geo/api/spec-source.html. An origin
35 * specifies the scheme, host and port of particular frame. An origin is
36 * represented here as a string, using the output of
37 * WebCore::SecurityOrigin::toString.
38 *
39 * This class is the Java counterpart of the WebKit C++ GeolocationPermissions
40 * class. It simply marshalls calls from the UI thread to the WebKit thread.
41 *
42 * Within WebKit, Geolocation permissions may be applied either temporarily
43 * (for the duration of the page) or permanently. This class deals only with
44 * permanent permissions.
45 */
46public final class GeolocationPermissions {
47    /**
48     * Callback interface used by the browser to report a Geolocation permission
49     * state set by the user in response to a permissions prompt.
50     */
51    public interface Callback {
52        public void invoke(String origin, boolean allow, boolean remember);
53    };
54
55    // Log tag
56    private static final String TAG = "geolocationPermissions";
57
58    // Global instance
59    private static GeolocationPermissions sInstance;
60
61    private Handler mHandler;
62    private Handler mUIHandler;
63
64    // Members used to transfer the origins and permissions between threads.
65    private Set<String> mOrigins;
66    private boolean mAllowed;
67    private Set<String> mOriginsToClear;
68    private Set<String> mOriginsToAllow;
69
70    // Message ids
71    static final int GET_ORIGINS = 0;
72    static final int GET_ALLOWED = 1;
73    static final int CLEAR = 2;
74    static final int ALLOW = 3;
75    static final int CLEAR_ALL = 4;
76
77    // Message ids on the UI thread
78    static final int RETURN_ORIGINS = 0;
79    static final int RETURN_ALLOWED = 1;
80
81    private static final String ORIGINS = "origins";
82    private static final String ORIGIN = "origin";
83    private static final String CALLBACK = "callback";
84    private static final String ALLOWED = "allowed";
85
86    /**
87     * Gets the singleton instance of the class.
88     */
89    public static GeolocationPermissions getInstance() {
90      if (sInstance == null) {
91          sInstance = new GeolocationPermissions();
92      }
93      return sInstance;
94    }
95
96    /**
97     * Creates the UI message handler. Must be called on the UI thread.
98     * @hide
99     */
100    public void createUIHandler() {
101        if (mUIHandler == null) {
102            mUIHandler = new Handler() {
103                @Override
104                public void handleMessage(Message msg) {
105                    // Runs on the UI thread.
106                    switch (msg.what) {
107                        case RETURN_ORIGINS: {
108                            Map values = (Map) msg.obj;
109                            Set<String> origins = (Set<String>) values.get(ORIGINS);
110                            ValueCallback<Set<String> > callback = (ValueCallback<Set<String> >) values.get(CALLBACK);
111                            callback.onReceiveValue(origins);
112                        } break;
113                        case RETURN_ALLOWED: {
114                            Map values = (Map) msg.obj;
115                            Boolean allowed = (Boolean) values.get(ALLOWED);
116                            ValueCallback<Boolean> callback = (ValueCallback<Boolean>) values.get(CALLBACK);
117                            callback.onReceiveValue(allowed);
118                        } break;
119                    }
120                }
121            };
122        }
123    }
124
125    /**
126     * Creates the message handler. Must be called on the WebKit thread.
127     * @hide
128     */
129    public synchronized void createHandler() {
130        if (mHandler == null) {
131            mHandler = new Handler() {
132                @Override
133                public void handleMessage(Message msg) {
134                    // Runs on the WebKit thread.
135                    switch (msg.what) {
136                        case GET_ORIGINS: {
137                            getOriginsImpl();
138                            ValueCallback callback = (ValueCallback) msg.obj;
139                            Map values = new HashMap<String, Object>();
140                            values.put(CALLBACK, callback);
141                            values.put(ORIGINS, mOrigins);
142                            postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
143                            } break;
144                        case GET_ALLOWED: {
145                            Map values = (Map) msg.obj;
146                            String origin = (String) values.get(ORIGIN);
147                            ValueCallback callback = (ValueCallback) values.get(CALLBACK);
148                            getAllowedImpl(origin);
149                            Map retValues = new HashMap<String, Object>();
150                            retValues.put(CALLBACK, callback);
151                            retValues.put(ALLOWED, new Boolean(mAllowed));
152                            postUIMessage(Message.obtain(null, RETURN_ALLOWED, retValues));
153                            } break;
154                        case CLEAR:
155                            nativeClear((String) msg.obj);
156                            break;
157                        case ALLOW:
158                            nativeAllow((String) msg.obj);
159                            break;
160                        case CLEAR_ALL:
161                            nativeClearAll();
162                            break;
163                    }
164                }
165            };
166
167            if (mOriginsToClear != null) {
168                for (String origin : mOriginsToClear) {
169                    nativeClear(origin);
170                }
171            }
172            if (mOriginsToAllow != null) {
173                for (String origin : mOriginsToAllow) {
174                    nativeAllow(origin);
175                }
176            }
177        }
178    }
179
180    /**
181     * Utility function to send a message to our handler.
182     */
183    private synchronized void postMessage(Message msg) {
184        assert(mHandler != null);
185        mHandler.sendMessage(msg);
186    }
187
188    /**
189     * Utility function to send a message to the handler on the UI thread
190     */
191    private void postUIMessage(Message msg) {
192        if (mUIHandler != null) {
193            mUIHandler.sendMessage(msg);
194        }
195    }
196
197    /**
198     * Gets the set of origins for which Geolocation permissions are stored.
199     * Note that we represent the origins as strings. These are created using
200     * WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules'
201     * (Database, Geolocation etc) do so, it's safe to match up origins based
202     * on this string.
203     *
204     * Callback is a ValueCallback object whose onReceiveValue method will be
205     * called asynchronously with the set of origins.
206     */
207    public void getOrigins(ValueCallback<Set<String> > callback) {
208        if (callback != null) {
209            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
210                getOriginsImpl();
211                callback.onReceiveValue(mOrigins);
212            } else {
213                postMessage(Message.obtain(null, GET_ORIGINS, callback));
214            }
215        }
216    }
217
218    /**
219     * Helper method to get the set of origins.
220     */
221    private void getOriginsImpl() {
222        // Called on the WebKit thread.
223        mOrigins = nativeGetOrigins();
224    }
225
226    /**
227     * Gets the permission state for the specified origin.
228     *
229     * Callback is a ValueCallback object whose onReceiveValue method will be
230     * called asynchronously with the permission state for the origin.
231     */
232    public void getAllowed(String origin, ValueCallback<Boolean> callback) {
233        if (callback == null) {
234            return;
235        }
236        if (origin == null) {
237            callback.onReceiveValue(null);
238            return;
239        }
240        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
241            getAllowedImpl(origin);
242            callback.onReceiveValue(new Boolean(mAllowed));
243        } else {
244            Map values = new HashMap<String, Object>();
245            values.put(ORIGIN, origin);
246            values.put(CALLBACK, callback);
247            postMessage(Message.obtain(null, GET_ALLOWED, values));
248        }
249    }
250
251    /**
252     * Helper method to get the permission state for the specified origin.
253     */
254    private void getAllowedImpl(String origin) {
255        // Called on the WebKit thread.
256        mAllowed = nativeGetAllowed(origin);
257    }
258
259    /**
260     * Clears the permission state for the specified origin. This method may be
261     * called before the WebKit thread has intialized the message handler.
262     * Messages will be queued until this time.
263     */
264    public void clear(String origin) {
265        // Called on the UI thread.
266        if (mHandler == null) {
267            if (mOriginsToClear == null) {
268                mOriginsToClear = new HashSet<String>();
269            }
270            mOriginsToClear.add(origin);
271            if (mOriginsToAllow != null) {
272                mOriginsToAllow.remove(origin);
273            }
274        } else {
275            postMessage(Message.obtain(null, CLEAR, origin));
276        }
277    }
278
279    /**
280     * Allows the specified origin. This method may be called before the WebKit
281     * thread has intialized the message handler. Messages will be queued until
282     * this time.
283     */
284    public void allow(String origin) {
285        // Called on the UI thread.
286        if (mHandler == null) {
287            if (mOriginsToAllow == null) {
288                mOriginsToAllow = new HashSet<String>();
289            }
290            mOriginsToAllow.add(origin);
291            if (mOriginsToClear != null) {
292                mOriginsToClear.remove(origin);
293            }
294        } else {
295            postMessage(Message.obtain(null, ALLOW, origin));
296        }
297    }
298
299    /**
300     * Clears the permission state for all origins.
301     */
302    public void clearAll() {
303        // Called on the UI thread.
304        postMessage(Message.obtain(null, CLEAR_ALL));
305    }
306
307    // Native functions, run on the WebKit thread.
308    private static native Set nativeGetOrigins();
309    private static native boolean nativeGetAllowed(String origin);
310    private static native void nativeClear(String origin);
311    private static native void nativeAllow(String origin);
312    private static native void nativeClearAll();
313}
314