1/*
2 * Copyright (C) 2010 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 com.android.browser;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.database.ContentObserver;
22import android.net.Uri;
23import android.os.AsyncTask;
24import android.os.Handler;
25import android.preference.PreferenceManager;
26import android.provider.Settings;
27import android.text.TextUtils;
28import android.webkit.GeolocationPermissions;
29import android.webkit.ValueCallback;
30
31import java.util.HashSet;
32import java.util.Set;
33
34/**
35 * Manages the interaction between the secure system setting for default geolocation
36 * permissions and the browser.
37 */
38class SystemAllowGeolocationOrigins {
39
40    // Preference key for the value of the system setting last read by the browser
41    private final static String LAST_READ_ALLOW_GEOLOCATION_ORIGINS =
42            "last_read_allow_geolocation_origins";
43
44    // The application context
45    private final Context mContext;
46
47    // The observer used to listen to the system setting.
48    private final SettingObserver mSettingObserver;
49
50    public SystemAllowGeolocationOrigins(Context context) {
51        mContext = context.getApplicationContext();
52        mSettingObserver = new SettingObserver();
53    }
54
55    /**
56     * Checks whether the setting has changed and installs an observer to listen for
57     * future changes. Must be called on the application main thread.
58     */
59    public void start() {
60        // Register to receive notifications when the system settings change.
61        Uri uri = Settings.Secure.getUriFor(Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS);
62        mContext.getContentResolver().registerContentObserver(uri, false, mSettingObserver);
63
64        // Read and apply the setting if needed.
65        maybeApplySettingAsync();
66    }
67
68    /**
69     * Stops the manager.
70     */
71    public void stop() {
72        mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
73    }
74
75    void maybeApplySettingAsync() {
76        BackgroundHandler.execute(mMaybeApplySetting);
77    }
78
79    /**
80     * Checks to see if the system setting has changed and if so,
81     * updates the Geolocation permissions accordingly.
82     */
83    private Runnable mMaybeApplySetting = new Runnable() {
84
85        @Override
86        public void run() {
87         // Get the new value
88            String newSetting = getSystemSetting();
89
90            // Get the last read value
91            SharedPreferences preferences = BrowserSettings.getInstance()
92                    .getPreferences();
93            String lastReadSetting =
94                    preferences.getString(LAST_READ_ALLOW_GEOLOCATION_ORIGINS, "");
95
96            // If the new value is the same as the last one we read, we're done.
97            if (TextUtils.equals(lastReadSetting, newSetting)) {
98                return;
99            }
100
101            // Save the new value as the last read value
102            preferences.edit()
103                    .putString(LAST_READ_ALLOW_GEOLOCATION_ORIGINS, newSetting)
104                    .apply();
105
106            Set<String> oldOrigins = parseAllowGeolocationOrigins(lastReadSetting);
107            Set<String> newOrigins = parseAllowGeolocationOrigins(newSetting);
108            Set<String> addedOrigins = setMinus(newOrigins, oldOrigins);
109            Set<String> removedOrigins = setMinus(oldOrigins, newOrigins);
110
111            // Remove the origins in the last read value
112            removeOrigins(removedOrigins);
113
114            // Add the origins in the new value
115            addOrigins(addedOrigins);
116        }
117    };
118
119    /**
120     * Parses the value of the default geolocation permissions setting.
121     *
122     * @param setting A space-separated list of origins.
123     * @return A mutable set of origins.
124     */
125    private static HashSet<String> parseAllowGeolocationOrigins(String setting) {
126        HashSet<String> origins = new HashSet<String>();
127        if (!TextUtils.isEmpty(setting)) {
128            for (String origin : setting.split("\\s+")) {
129                if (!TextUtils.isEmpty(origin)) {
130                    origins.add(origin);
131                }
132            }
133        }
134        return origins;
135    }
136
137    /**
138     * Gets the difference between two sets. Does not modify any of the arguments.
139     *
140     * @return A set containing all elements in {@code x} that are not in {@code y}.
141     */
142    private <A> Set<A> setMinus(Set<A> x, Set<A> y) {
143        HashSet<A> z = new HashSet<A>(x.size());
144        for (A a : x) {
145            if (!y.contains(a)) {
146                z.add(a);
147            }
148        }
149        return z;
150    }
151
152    /**
153     * Gets the current system setting for default allowed geolocation origins.
154     *
155     * @return The default allowed origins. Returns {@code ""} if not set.
156     */
157    private String getSystemSetting() {
158        String value = Settings.Secure.getString(mContext.getContentResolver(),
159                Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS);
160        return value == null ? "" : value;
161    }
162
163    /**
164     * Adds geolocation permissions for the given origins.
165     */
166    private void addOrigins(Set<String> origins) {
167        for (String origin : origins) {
168            GeolocationPermissions.getInstance().allow(origin);
169        }
170    }
171
172    /**
173     * Removes geolocation permissions for the given origins, if they are allowed.
174     * If they are denied or not set, nothing is done.
175     */
176    private void removeOrigins(Set<String> origins) {
177        for (final String origin : origins) {
178            GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
179                public void onReceiveValue(Boolean value) {
180                    if (value != null && value.booleanValue()) {
181                        GeolocationPermissions.getInstance().clear(origin);
182                    }
183                }
184            });
185        }
186    }
187
188    /**
189     * Listens for changes to the system setting.
190     */
191    private class SettingObserver extends ContentObserver {
192
193        SettingObserver() {
194            super(new Handler());
195        }
196
197        @Override
198        public void onChange(boolean selfChange) {
199            maybeApplySettingAsync();
200        }
201    }
202
203}
204