| 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 |