OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.webapps; | 5 package org.chromium.chrome.browser.webapps; |
6 | 6 |
7 import android.content.Context; | 7 import android.content.Context; |
8 import android.content.SharedPreferences; | 8 import android.content.SharedPreferences; |
9 import android.content.pm.PackageManager; | 9 import android.content.pm.PackageManager; |
10 import android.content.pm.PackageManager.NameNotFoundException; | 10 import android.content.pm.PackageManager.NameNotFoundException; |
11 import android.os.AsyncTask; | 11 import android.os.AsyncTask; |
12 | 12 |
13 import org.chromium.base.ContextUtils; | 13 import org.chromium.base.ContextUtils; |
| 14 import org.chromium.base.ThreadUtils; |
14 import org.chromium.base.VisibleForTesting; | 15 import org.chromium.base.VisibleForTesting; |
15 import org.chromium.base.annotations.CalledByNative; | 16 import org.chromium.base.annotations.CalledByNative; |
16 import org.chromium.chrome.browser.browsing_data.UrlFilter; | 17 import org.chromium.chrome.browser.browsing_data.UrlFilter; |
17 import org.chromium.chrome.browser.browsing_data.UrlFilterBridge; | 18 import org.chromium.chrome.browser.browsing_data.UrlFilterBridge; |
18 | 19 |
19 import java.util.Collections; | 20 import java.util.Collections; |
20 import java.util.HashMap; | 21 import java.util.HashSet; |
21 import java.util.Iterator; | |
22 import java.util.Set; | 22 import java.util.Set; |
23 import java.util.concurrent.TimeUnit; | 23 import java.util.concurrent.TimeUnit; |
24 | 24 |
25 /** | 25 /** |
26 * Singleton class which tracks web apps backed by a SharedPreferences file (abs
tracted by the | 26 * Keeps track of web apps which have created a SharedPreference file (through t
he used of the |
27 * WebappDataStorage class). This class must be used on the main thread, except
when warming | 27 * WebappDataStorage class) which may need to be cleaned up in the future. |
28 * SharedPreferences. | |
29 * | 28 * |
30 * Aside from web app registration, which is asynchronous as a new SharedPrefere
nces file must be | 29 * It is NOT intended to be 100% accurate nor a comprehensive list of all instal
led web apps |
31 * opened, all methods in this class are synchronous. All web app SharedPreferen
ces known to | 30 * because it is impossible to track when the user removes a web app from the Ho
me screen and it |
32 * WebappRegistry are pre-warmed on browser startup when creating the singleton
WebappRegistry | 31 * is similarily impossible to track pre-registry era web apps (this case is not
a problem anyway |
33 * instance, whilst registering a new web app will automatically cache the new S
haredPreferences | 32 * as these web apps have no external data to cleanup). |
34 * after it is created. | |
35 * | |
36 * This class is not a comprehensive list of installed web apps because it is im
possible to know | |
37 * when the user removes a web app from the home screen. The WebappDataStorage.w
asLaunchedRecently() | |
38 * heuristic attempts to compensate for this. | |
39 */ | 33 */ |
40 public class WebappRegistry { | 34 public class WebappRegistry { |
41 | 35 |
42 static final String REGISTRY_FILE_NAME = "webapp_registry"; | 36 static final String REGISTRY_FILE_NAME = "webapp_registry"; |
43 static final String KEY_WEBAPP_SET = "webapp_set"; | 37 static final String KEY_WEBAPP_SET = "webapp_set"; |
44 static final String KEY_LAST_CLEANUP = "last_cleanup"; | 38 static final String KEY_LAST_CLEANUP = "last_cleanup"; |
45 | 39 |
46 /** Represents a period of 4 weeks in milliseconds */ | 40 /** Represents a period of 4 weeks in milliseconds */ |
47 static final long FULL_CLEANUP_DURATION = TimeUnit.DAYS.toMillis(4L * 7L); | 41 static final long FULL_CLEANUP_DURATION = TimeUnit.DAYS.toMillis(4L * 7L); |
48 | 42 |
49 /** Represents a period of 13 weeks in milliseconds */ | 43 /** Represents a period of 13 weeks in milliseconds */ |
50 static final long WEBAPP_UNOPENED_CLEANUP_DURATION = TimeUnit.DAYS.toMillis(
13L * 7L); | 44 static final long WEBAPP_UNOPENED_CLEANUP_DURATION = TimeUnit.DAYS.toMillis(
13L * 7L); |
51 | 45 |
52 private static volatile WebappRegistry sInstance; | 46 /** |
53 | 47 * Called when a retrieval of the set of stored web app IDs occurs. |
54 private HashMap<String, WebappDataStorage> mStorages; | 48 */ |
55 private SharedPreferences mPreferences; | 49 public interface FetchCallback { |
| 50 void onWebappIdsRetrieved(Set<String> readObject); |
| 51 } |
56 | 52 |
57 /** | 53 /** |
58 * Callback run when a WebappDataStorage object is registered for the first
time. The storage | 54 * Called when a retrieval of the stored WebappDataStorage occurs. The stora
ge parameter will |
59 * parameter will never be null. | 55 * be null if the web app queried for was not in the registry. |
60 */ | 56 */ |
61 public interface FetchWebappDataStorageCallback { | 57 public interface FetchWebappDataStorageCallback { |
62 void onWebappDataStorageRetrieved(WebappDataStorage storage); | 58 void onWebappDataStorageRetrieved(WebappDataStorage storage); |
63 } | 59 } |
64 | 60 |
65 /** | 61 /** |
66 * Returns the singleton WebappRegistry instance. Creates the instance if ne
cessary. | |
67 */ | |
68 public static WebappRegistry getInstance() { | |
69 if (sInstance == null) sInstance = new WebappRegistry(); | |
70 return sInstance; | |
71 } | |
72 | |
73 /** | |
74 * Warm up the WebappRegistry and a specific WebappDataStorage SharedPrefere
nces. This static | |
75 * method can be called on any thread, so it must not initialize sInstance. | |
76 * @param id The web app id to warm up in addition to the WebappRegistry. | |
77 */ | |
78 public static void warmUpSharedPrefsForId(String id) { | |
79 sInstance.initStorages(id, false); | |
80 } | |
81 | |
82 /** | |
83 * Warm up the WebappRegistry and all WebappDataStorage SharedPreferences. T
his static method | |
84 * can be called on any thread, so it must not initialize sInstance. | |
85 */ | |
86 public static void warmUpSharedPrefs() { | |
87 sInstance.initStorages(null, false); | |
88 } | |
89 | |
90 public static void refreshSharedPrefsForTesting() { | |
91 sInstance = new WebappRegistry(); | |
92 sInstance.initStorages(null, true); | |
93 } | |
94 | |
95 /** | |
96 * Registers the existence of a web app, creates a SharedPreference entry fo
r it, and runs the | 62 * Registers the existence of a web app, creates a SharedPreference entry fo
r it, and runs the |
97 * supplied callback (if not null) on the UI thread with the resulting Webap
pDataStorage object. | 63 * supplied callback (if not null) on the UI thread with the resulting Webap
pDataStorage object. |
98 * @param webappId The id of the web app to register. | 64 * @param webappId The id of the web app to register. |
99 * @param callback The callback to run with the WebappDataStorage argument. | 65 * @param callback The callback to run with the WebappDataStorage argument. |
100 * @return The storage object for the web app. | 66 * @return The storage object for the web app. |
101 */ | 67 */ |
102 public void register(final String webappId, final FetchWebappDataStorageCall
back callback) { | 68 public static void registerWebapp(final String webappId, |
| 69 final FetchWebappDataStorageCallback callback) { |
103 new AsyncTask<Void, Void, WebappDataStorage>() { | 70 new AsyncTask<Void, Void, WebappDataStorage>() { |
104 @Override | 71 @Override |
105 protected final WebappDataStorage doInBackground(Void... nothing) { | 72 protected final WebappDataStorage doInBackground(Void... nothing) { |
106 // Create the WebappDataStorage on the background thread, as thi
s must create and | 73 SharedPreferences preferences = openSharedPreferences(); |
107 // open a new SharedPreferences. | 74 // The set returned by getRegisteredWebappIds must be treated as
immutable, so we |
108 return WebappDataStorage.open(webappId); | 75 // make a copy to edit and save. |
| 76 Set<String> webapps = new HashSet<>(getRegisteredWebappIds(prefe
rences)); |
| 77 boolean added = webapps.add(webappId); |
| 78 assert added; |
| 79 |
| 80 preferences.edit().putStringSet(KEY_WEBAPP_SET, webapps).apply()
; |
| 81 |
| 82 // Create the WebappDataStorage and update the last used time, s
o we can guarantee |
| 83 // that a web app which appears in the registry will have a |
| 84 // last used time != WebappDataStorage.LAST_USED_INVALID. |
| 85 WebappDataStorage storage = new WebappDataStorage(webappId); |
| 86 storage.updateLastUsedTime(); |
| 87 return storage; |
109 } | 88 } |
110 | 89 |
111 @Override | 90 @Override |
112 protected final void onPostExecute(WebappDataStorage storage) { | 91 protected final void onPostExecute(WebappDataStorage storage) { |
113 // Guarantee that last used time != WebappDataStorage.LAST_USED_
INVALID. Must be | |
114 // run on the main thread as SharedPreferences.Editor.apply() is
called. | |
115 mStorages.put(webappId, storage); | |
116 mPreferences.edit().putStringSet(KEY_WEBAPP_SET, mStorages.keySe
t()).apply(); | |
117 storage.updateLastUsedTime(); | |
118 if (callback != null) callback.onWebappDataStorageRetrieved(stor
age); | 92 if (callback != null) callback.onWebappDataStorageRetrieved(stor
age); |
119 } | 93 } |
120 }.execute(); | 94 }.execute(); |
121 } | 95 } |
122 | 96 |
123 /** | 97 /** |
124 * Returns the WebappDataStorage object for webappId, or null if one cannot
be found. | 98 * Runs the callback, supplying the WebappDataStorage object for webappId, o
r null if the web |
125 * @param webappId The id of the web app. | 99 * app has not been registered. |
| 100 * @param webappId The id of the web app to register. |
126 * @return The storage object for the web app, or null if webappId is not re
gistered. | 101 * @return The storage object for the web app, or null if webappId is not re
gistered. |
127 */ | 102 */ |
128 public WebappDataStorage getWebappDataStorage(String webappId) { | 103 public static void getWebappDataStorage(final String webappId, |
129 return mStorages.get(webappId); | 104 final FetchWebappDataStorageCallback callback) { |
130 } | 105 new AsyncTask<Void, Void, WebappDataStorage>() { |
131 | 106 @Override |
132 /** | 107 protected final WebappDataStorage doInBackground(Void... nothing) { |
133 * Returns the WebappDataStorage object whose scope most closely matches the
provided URL, or | 108 SharedPreferences preferences = openSharedPreferences(); |
134 * null if a matching web app cannot be found. The most closely matching sco
pe is the longest | 109 if (getRegisteredWebappIds(preferences).contains(webappId)) { |
135 * scope which has the same prefix as the URL to open. | 110 WebappDataStorage storage = WebappDataStorage.open(webappId)
; |
136 * @param url The URL to search for. | 111 return storage; |
137 * @return The storage object for the web app, or null if one cannot be foun
d. | 112 } |
138 */ | 113 return null; |
139 public WebappDataStorage getWebappDataStorageForUrl(final String url) { | 114 } |
140 WebappDataStorage bestMatch = null; | 115 |
141 int largestOverlap = 0; | 116 @Override |
142 for (HashMap.Entry<String, WebappDataStorage> entry : mStorages.entrySet
()) { | 117 protected final void onPostExecute(WebappDataStorage storage) { |
143 WebappDataStorage storage = entry.getValue(); | 118 assert callback != null; |
144 String scope = storage.getScope(); | 119 callback.onWebappDataStorageRetrieved(storage); |
145 if (url.startsWith(scope) && scope.length() > largestOverlap) { | 120 } |
146 bestMatch = storage; | 121 }.execute(); |
147 largestOverlap = scope.length(); | 122 } |
148 } | 123 |
149 } | 124 /** |
150 return bestMatch; | 125 * Runs the callback, supplying the WebappDataStorage object whose scope mos
t closely matches |
151 } | 126 * the provided URL, or null if a matching web app cannot be found. The most
closely matching |
152 | 127 * scope is the longest scope which has the same prefix as the URL to open. |
153 /** | 128 * @param url The URL to search for. |
154 * Returns the list of web app IDs which are written to SharedPreferences. | 129 * @return The storage object for the web app, or null if webappId is not re
gistered. |
| 130 */ |
| 131 public static void getWebappDataStorageForUrl(final String url, |
| 132 final FetchWebappDataStorageCallback callback) { |
| 133 new AsyncTask<Void, Void, WebappDataStorage>() { |
| 134 @Override |
| 135 protected final WebappDataStorage doInBackground(Void... nothing) { |
| 136 SharedPreferences preferences = openSharedPreferences(); |
| 137 WebappDataStorage bestMatch = null; |
| 138 int largestOverlap = 0; |
| 139 for (String id : getRegisteredWebappIds(preferences)) { |
| 140 WebappDataStorage storage = WebappDataStorage.open(id); |
| 141 String scope = storage.getScope(); |
| 142 if (url.startsWith(scope) && scope.length() > largestOverlap
) { |
| 143 bestMatch = storage; |
| 144 largestOverlap = scope.length(); |
| 145 } |
| 146 } |
| 147 return bestMatch; |
| 148 } |
| 149 |
| 150 protected final void onPostExecute(WebappDataStorage storage) { |
| 151 assert callback != null; |
| 152 callback.onWebappDataStorageRetrieved(storage); |
| 153 } |
| 154 }.execute(); |
| 155 } |
| 156 |
| 157 /** |
| 158 * Asynchronously retrieves the list of web app IDs which this registry is a
ware of. |
| 159 * @param callback Called when the set has been retrieved. The set may be em
pty. |
155 */ | 160 */ |
156 @VisibleForTesting | 161 @VisibleForTesting |
157 public static Set<String> getRegisteredWebappIdsForTesting() { | 162 public static void getRegisteredWebappIds(final FetchCallback callback) { |
158 // Wrap with unmodifiableSet to ensure it's never modified. See crbug.co
m/568369. | 163 new AsyncTask<Void, Void, Set<String>>() { |
159 return Collections.unmodifiableSet(openSharedPreferences().getStringSet( | 164 @Override |
160 KEY_WEBAPP_SET, Collections.<String>emptySet())); | 165 protected final Set<String> doInBackground(Void... nothing) { |
161 } | 166 return getRegisteredWebappIds(openSharedPreferences()); |
162 | 167 } |
163 /** | 168 |
164 * Deletes the data for all "old" web apps, as well as all WebAPKs that have
been uninstalled in | 169 @Override |
165 * the last month. "Old" web apps have not been opened by the user in the la
st 3 months, or have | 170 protected final void onPostExecute(Set<String> result) { |
166 * had their last used time set to 0 by the user clearing their history. Cle
anup is run, at | 171 assert callback != null; |
167 * most, once a month. | 172 callback.onWebappIdsRetrieved(result); |
| 173 } |
| 174 }.execute(); |
| 175 } |
| 176 |
| 177 /** |
| 178 * 1. Deletes the data for all "old" web apps. |
| 179 * "Old" web apps have not been opened by the user in the last 3 months, or
have had their last |
| 180 * used time set to 0 by the user clearing their history. Cleanup is run, at
most, once a month. |
| 181 * 2. Deletes the data for all WebAPKs that have been uninstalled in the las
t month. |
| 182 * |
168 * @param currentTime The current time which will be checked to decide if th
e task should be run | 183 * @param currentTime The current time which will be checked to decide if th
e task should be run |
169 * and if a web app should be cleaned up. | 184 * and if a web app should be cleaned up. |
170 */ | 185 */ |
171 public void unregisterOldWebapps(long currentTime) { | 186 static void unregisterOldWebapps(final long currentTime) { |
172 if ((currentTime - mPreferences.getLong(KEY_LAST_CLEANUP, 0)) < FULL_CLE
ANUP_DURATION) { | 187 new AsyncTask<Void, Void, Void>() { |
173 return; | 188 @Override |
174 } | 189 protected final Void doInBackground(Void... nothing) { |
175 | 190 SharedPreferences preferences = openSharedPreferences(); |
176 Iterator<HashMap.Entry<String, WebappDataStorage>> it = mStorages.entryS
et().iterator(); | 191 long lastCleanup = preferences.getLong(KEY_LAST_CLEANUP, 0); |
177 while (it.hasNext()) { | 192 if ((currentTime - lastCleanup) < FULL_CLEANUP_DURATION) return
null; |
178 HashMap.Entry<String, WebappDataStorage> entry = it.next(); | 193 |
179 WebappDataStorage storage = entry.getValue(); | 194 Set<String> currentWebapps = getRegisteredWebappIds(preferences)
; |
180 String webApkPackage = storage.getWebApkPackageName(); | 195 Set<String> retainedWebapps = new HashSet<>(currentWebapps); |
181 if (webApkPackage != null) { | 196 PackageManager pm = ContextUtils.getApplicationContext().getPack
ageManager(); |
182 if (isWebApkInstalled(webApkPackage)) { | 197 for (String id : currentWebapps) { |
183 continue; | 198 WebappDataStorage storage = new WebappDataStorage(id); |
184 } | 199 String webApkPackage = storage.getWebApkPackageName(); |
185 } else if ((currentTime - storage.getLastUsedTime()) | 200 if (webApkPackage != null) { |
186 < WEBAPP_UNOPENED_CLEANUP_DURATION) { | 201 if (isWebApkInstalled(pm, webApkPackage)) continue; |
187 continue; | 202 } else { |
188 } | 203 long lastUsed = storage.getLastUsedTime(); |
189 storage.delete(); | 204 if ((currentTime - lastUsed) < WEBAPP_UNOPENED_CLEANUP_D
URATION) continue; |
190 it.remove(); | 205 } |
191 } | 206 WebappDataStorage.deleteDataForWebapp(id); |
192 | 207 retainedWebapps.remove(id); |
193 mPreferences.edit() | 208 } |
194 .putLong(KEY_LAST_CLEANUP, currentTime) | 209 |
195 .putStringSet(KEY_WEBAPP_SET, mStorages.keySet()) | 210 preferences.edit() |
196 .apply(); | 211 .putLong(KEY_LAST_CLEANUP, currentTime) |
197 } | 212 .putStringSet(KEY_WEBAPP_SET, retainedWebapps) |
198 | 213 .apply(); |
199 /** | 214 return null; |
200 * Deletes the data of all web apps whose url matches |urlFilter|. | 215 } |
201 * @param urlFilter The filter object to check URLs. | 216 }.execute(); |
202 */ | 217 } |
203 @VisibleForTesting | 218 |
204 void unregisterWebappsForUrlsImpl(UrlFilter urlFilter) { | 219 /** |
205 Iterator<HashMap.Entry<String, WebappDataStorage>> it = mStorages.entryS
et().iterator(); | 220 * Returns whether the given WebAPK is still installed. |
206 while (it.hasNext()) { | 221 */ |
207 HashMap.Entry<String, WebappDataStorage> entry = it.next(); | 222 private static boolean isWebApkInstalled(PackageManager pm, String webApkPac
kage) { |
208 WebappDataStorage storage = entry.getValue(); | 223 assert !ThreadUtils.runningOnUiThread(); |
209 if (urlFilter.matchesUrl(storage.getUrl())) { | |
210 storage.delete(); | |
211 it.remove(); | |
212 } | |
213 } | |
214 | |
215 if (mStorages.isEmpty()) { | |
216 mPreferences.edit().clear().apply(); | |
217 } else { | |
218 mPreferences.edit().putStringSet(KEY_WEBAPP_SET, mStorages.keySet())
.apply(); | |
219 } | |
220 } | |
221 | |
222 @CalledByNative | |
223 static void unregisterWebappsForUrls(UrlFilterBridge urlFilter) { | |
224 WebappRegistry.getInstance().unregisterWebappsForUrlsImpl(urlFilter); | |
225 urlFilter.destroy(); | |
226 } | |
227 | |
228 /** | |
229 * Deletes the URL and scope, and sets the last used time to 0 for all web a
pps whose url | |
230 * matches |urlFilter|. | |
231 * @param urlFilter The filter object to check URLs. | |
232 */ | |
233 @VisibleForTesting | |
234 void clearWebappHistoryForUrlsImpl(UrlFilter urlFilter) { | |
235 for (HashMap.Entry<String, WebappDataStorage> entry : mStorages.entrySet
()) { | |
236 WebappDataStorage storage = entry.getValue(); | |
237 if (urlFilter.matchesUrl(storage.getUrl())) { | |
238 storage.clearHistory(); | |
239 } | |
240 } | |
241 } | |
242 | |
243 @CalledByNative | |
244 static void clearWebappHistoryForUrls(UrlFilterBridge urlFilter) { | |
245 WebappRegistry.getInstance().clearWebappHistoryForUrlsImpl(urlFilter); | |
246 urlFilter.destroy(); | |
247 } | |
248 | |
249 /** | |
250 * Returns true if the given WebAPK is installed. | |
251 */ | |
252 private boolean isWebApkInstalled(String webApkPackage) { | |
253 try { | 224 try { |
254 ContextUtils.getApplicationContext().getPackageManager().getPackageI
nfo( | 225 pm.getPackageInfo(webApkPackage, PackageManager.GET_ACTIVITIES); |
255 webApkPackage, PackageManager.GET_ACTIVITIES); | |
256 } catch (NameNotFoundException e) { | 226 } catch (NameNotFoundException e) { |
257 return false; | 227 return false; |
258 } | 228 } |
259 return true; | 229 return true; |
260 } | 230 } |
261 | 231 |
| 232 /** |
| 233 * Deletes the data of all web apps whose url matches |urlFilter|, as well a
s the registry |
| 234 * tracking those web apps. |
| 235 */ |
| 236 @VisibleForTesting |
| 237 static void unregisterWebappsForUrls(final UrlFilter urlFilter, final Runnab
le callback) { |
| 238 new AsyncTask<Void, Void, Void>() { |
| 239 @Override |
| 240 protected final Void doInBackground(Void... nothing) { |
| 241 SharedPreferences preferences = openSharedPreferences(); |
| 242 Set<String> registeredWebapps = |
| 243 new HashSet<>(getRegisteredWebappIds(preferences)); |
| 244 Set<String> webappsToUnregister = new HashSet<>(); |
| 245 for (String id : registeredWebapps) { |
| 246 if (urlFilter.matchesUrl(WebappDataStorage.open(id).getUrl()
)) { |
| 247 WebappDataStorage.deleteDataForWebapp(id); |
| 248 webappsToUnregister.add(id); |
| 249 } |
| 250 } |
| 251 |
| 252 // TODO(dominickn): SharedPreferences should be accessed on the
main thread, not |
| 253 // from an AsyncTask. Simultaneous access from two threads creat
es a race condition. |
| 254 // Update all callsites in this class. |
| 255 registeredWebapps.removeAll(webappsToUnregister); |
| 256 if (registeredWebapps.isEmpty()) { |
| 257 preferences.edit().clear().apply(); |
| 258 } else { |
| 259 preferences.edit().putStringSet(KEY_WEBAPP_SET, registeredWe
bapps).apply(); |
| 260 } |
| 261 |
| 262 return null; |
| 263 } |
| 264 |
| 265 @Override |
| 266 protected final void onPostExecute(Void nothing) { |
| 267 assert callback != null; |
| 268 callback.run(); |
| 269 } |
| 270 }.execute(); |
| 271 } |
| 272 |
| 273 @CalledByNative |
| 274 static void unregisterWebappsForUrls( |
| 275 final UrlFilterBridge urlFilter, final long callbackPointer) { |
| 276 unregisterWebappsForUrls(urlFilter, new Runnable() { |
| 277 @Override |
| 278 public void run() { |
| 279 urlFilter.destroy(); |
| 280 nativeOnWebappsUnregistered(callbackPointer); |
| 281 } |
| 282 }); |
| 283 } |
| 284 |
| 285 /** |
| 286 * Deletes the URL and scope, and sets the last used time to 0 for all web a
pps whose url |
| 287 * matches |urlFilter|. |
| 288 */ |
| 289 @VisibleForTesting |
| 290 static void clearWebappHistoryForUrls(final UrlFilter urlFilter, final Runna
ble callback) { |
| 291 new AsyncTask<Void, Void, Void>() { |
| 292 @Override |
| 293 protected final Void doInBackground(Void... nothing) { |
| 294 SharedPreferences preferences = openSharedPreferences(); |
| 295 for (String id : getRegisteredWebappIds(preferences)) { |
| 296 if (urlFilter.matchesUrl(WebappDataStorage.open(id).getUrl()
)) { |
| 297 WebappDataStorage.clearHistory(id); |
| 298 } |
| 299 } |
| 300 return null; |
| 301 } |
| 302 |
| 303 @Override |
| 304 protected final void onPostExecute(Void nothing) { |
| 305 assert callback != null; |
| 306 callback.run(); |
| 307 } |
| 308 }.execute(); |
| 309 } |
| 310 |
| 311 @CalledByNative |
| 312 static void clearWebappHistoryForUrls( |
| 313 final UrlFilterBridge urlFilter, final long callbackPointer) { |
| 314 clearWebappHistoryForUrls(urlFilter, new Runnable() { |
| 315 @Override |
| 316 public void run() { |
| 317 urlFilter.destroy(); |
| 318 nativeOnClearedWebappHistory(callbackPointer); |
| 319 } |
| 320 }); |
| 321 } |
| 322 |
262 private static SharedPreferences openSharedPreferences() { | 323 private static SharedPreferences openSharedPreferences() { |
263 return ContextUtils.getApplicationContext().getSharedPreferences( | 324 return ContextUtils.getApplicationContext().getSharedPreferences( |
264 REGISTRY_FILE_NAME, Context.MODE_PRIVATE); | 325 REGISTRY_FILE_NAME, Context.MODE_PRIVATE); |
265 } | 326 } |
266 | 327 |
| 328 private static Set<String> getRegisteredWebappIds(SharedPreferences preferen
ces) { |
| 329 // Wrap with unmodifiableSet to ensure it's never modified. See crbug.co
m/568369. |
| 330 return Collections.unmodifiableSet( |
| 331 preferences.getStringSet(KEY_WEBAPP_SET, Collections.<String>emp
tySet())); |
| 332 } |
| 333 |
267 private WebappRegistry() { | 334 private WebappRegistry() { |
268 mPreferences = openSharedPreferences(); | 335 } |
269 mStorages = new HashMap<String, WebappDataStorage>(); | 336 |
270 } | 337 private static native void nativeOnWebappsUnregistered(long callbackPointer)
; |
271 | 338 private static native void nativeOnClearedWebappHistory(long callbackPointer
); |
272 private void initStorages(String idToInitialize, boolean replaceExisting) { | |
273 Set<String> webapps = | |
274 mPreferences.getStringSet(KEY_WEBAPP_SET, Collections.<String>em
ptySet()); | |
275 boolean initAll = (idToInitialize == null || idToInitialize.isEmpty()); | |
276 | |
277 // Don't overwrite any entry in mStorages unless replaceExisting is set
to true. | |
278 if (initAll) { | |
279 for (String id : webapps) { | |
280 if (replaceExisting || !mStorages.containsKey(id)) { | |
281 mStorages.put(id, WebappDataStorage.open(id)); | |
282 } | |
283 } | |
284 } else { | |
285 if (webapps.contains(idToInitialize) | |
286 && (replaceExisting || !mStorages.containsKey(idToInitialize
))) { | |
287 mStorages.put(idToInitialize, WebappDataStorage.open(idToInitial
ize)); | |
288 } | |
289 } | |
290 } | |
291 } | 339 } |
OLD | NEW |