| Index: chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
|
| index fabf9fb1324426f58f60bdc5ddafc3386f95350c..6980e423aa55f5bbe7a7727237edbdd454200045 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
|
| @@ -5,6 +5,7 @@
|
| package org.chromium.chrome.browser.webapps;
|
|
|
| import android.content.Context;
|
| +import android.content.Intent;
|
| import android.content.SharedPreferences;
|
| import android.graphics.Bitmap;
|
| import android.os.AsyncTask;
|
| @@ -12,63 +13,66 @@ import android.os.AsyncTask;
|
| import org.chromium.base.ThreadUtils;
|
| import org.chromium.base.VisibleForTesting;
|
| import org.chromium.chrome.browser.ShortcutHelper;
|
| +import org.chromium.chrome.browser.ShortcutSource;
|
| +import org.chromium.chrome.browser.util.IntentUtils;
|
| +import org.chromium.content_public.common.ScreenOrientationValues;
|
|
|
| import java.util.Map;
|
|
|
| /**
|
| * Stores data about an installed web app. Uses SharedPreferences to persist the data to disk.
|
| - * Before this class is used, the web app must be registered in {@link WebappRegistry}.
|
| - *
|
| - * EXAMPLE USAGE:
|
| - *
|
| - * (1) UPDATING/RETRIEVING THE ICON (web app MUST have been registered in WebappRegistry)
|
| - * WebappDataStorage storage = WebappDataStorage.open(context, id);
|
| - * storage.updateSplashScreenImage(bitmap);
|
| - * storage.getSplashScreenImage(callback);
|
| + * This class must only be accessed via {@link WebappRegistry}, which is used to register and keep
|
| + * track of web app data known to Chrome.
|
| */
|
| public class WebappDataStorage {
|
|
|
| static final String SHARED_PREFS_FILE_PREFIX = "webapp_";
|
| static final String KEY_SPLASH_ICON = "splash_icon";
|
| static final String KEY_LAST_USED = "last_used";
|
| + static final String KEY_URL = "url";
|
| static final String KEY_SCOPE = "scope";
|
| + static final String KEY_ICON = "icon";
|
| + static final String KEY_NAME = "name";
|
| + static final String KEY_SHORT_NAME = "short_name";
|
| + static final String KEY_ORIENTATION = "orientation";
|
| + static final String KEY_THEME_COLOR = "theme_color";
|
| + static final String KEY_BACKGROUND_COLOR = "background_color";
|
| + static final String KEY_SOURCE = "source";
|
| + static final String KEY_ACTION = "action";
|
| + static final String KEY_IS_ICON_GENERATED = "is_icon_generated";
|
| + static final String KEY_VERSION = "version";
|
|
|
| - // Unset/invalid constants for last used times and scopes. 0 is used as the null last
|
| + // Unset/invalid constants for last used times and URLs. 0 is used as the null last
|
| // used time as WebappRegistry assumes that this is always a valid timestamp.
|
| static final long LAST_USED_UNSET = 0;
|
| static final long LAST_USED_INVALID = -1;
|
| - static final String SCOPE_INVALID = "";
|
| + static final String URL_INVALID = "";
|
| + static final int VERSION_INVALID = 0;
|
|
|
| private static Factory sFactory = new Factory();
|
|
|
| + private final String mId;
|
| private final SharedPreferences mPreferences;
|
|
|
| /**
|
| - * Opens an instance of WebappDataStorage for the web app specified.
|
| + * Opens an instance of WebappDataStorage for the web app specified. Must not be run on the UI
|
| + * thread.
|
| * @param context The context to open the SharedPreferences.
|
| * @param webappId The ID of the web app which is being opened.
|
| */
|
| - public static WebappDataStorage open(final Context context, final String webappId) {
|
| + static WebappDataStorage open(final Context context, final String webappId) {
|
| final WebappDataStorage storage = sFactory.create(context, webappId);
|
| - new AsyncTask<Void, Void, Void>() {
|
| - @Override
|
| - protected final Void doInBackground(Void... nothing) {
|
| - if (storage.getLastUsedTime() == LAST_USED_INVALID) {
|
| - // If the last used time is invalid then assert that there is no data
|
| - // in the WebappDataStorage which needs to be cleaned up.
|
| - assert storage.getAllData().isEmpty();
|
| - } else {
|
| - storage.updateLastUsedTime();
|
| - }
|
| - return null;
|
| - }
|
| - }.execute();
|
| + if (storage.getLastUsedTime() == LAST_USED_INVALID) {
|
| + // If the last used time is invalid then ensure that there is no data in the
|
| + // WebappDataStorage which needs to be cleaned up.
|
| + assert storage.getAllData().isEmpty();
|
| + }
|
| return storage;
|
| }
|
|
|
| /**
|
| - * Asynchronously retrieves the time which this WebappDataStorage was last
|
| - * opened using {@link WebappDataStorage#open(Context, String)}.
|
| + * Asynchronously retrieves the time which this WebappDataStorage was last opened. Used in
|
| + * testing.
|
| * @param context The context to read the SharedPreferences file.
|
| * @param webappId The ID of the web app the used time is being read for.
|
| * @param callback Called when the last used time has been retrieved.
|
| @@ -87,6 +91,7 @@ public class WebappDataStorage {
|
|
|
| @Override
|
| protected final void onPostExecute(Long lastUsed) {
|
| + assert callback != null;
|
| callback.onDataRetrieved(lastUsed);
|
| }
|
| }.execute();
|
| @@ -94,7 +99,7 @@ public class WebappDataStorage {
|
|
|
| /**
|
| * Asynchronously retrieves the scope stored in this WebappDataStorage. The scope is the URL
|
| - * over which the webapp data is applied to.
|
| + * over which the web app data is applied to. Used in testing.
|
| * @param context The context to read the SharedPreferences file.
|
| * @param webappId The ID of the web app the used time is being read for.
|
| * @param callback Called when the scope has been retrieved.
|
| @@ -105,32 +110,36 @@ public class WebappDataStorage {
|
| new AsyncTask<Void, Void, String>() {
|
| @Override
|
| protected final String doInBackground(Void... nothing) {
|
| - return new WebappDataStorage(context.getApplicationContext(), webappId)
|
| - .getScope();
|
| + return new WebappDataStorage(context.getApplicationContext(), webappId).getScope();
|
| }
|
|
|
| @Override
|
| protected final void onPostExecute(String scope) {
|
| + assert callback != null;
|
| callback.onDataRetrieved(scope);
|
| }
|
| }.execute();
|
| }
|
|
|
| /**
|
| - * Asynchronously sets the scope stored in this WebappDataStorage. Does nothing if there
|
| - * is already a scope stored; since webapps added to homescreen cannot change the scope which
|
| - * they launch, it is not intended that a WebappDataStorage will be able to change the scope
|
| - * once it is set.
|
| + * Asynchronously retrieves the URL stored in this WebappDataStorage. Used in testing.
|
| * @param context The context to read the SharedPreferences file.
|
| * @param webappId The ID of the web app the used time is being read for.
|
| - * @param scope The scope to set for the web app.
|
| + * @param callback Called when the URL has been retrieved.
|
| */
|
| - public static void setScope(final Context context, final String webappId, final String scope) {
|
| - new AsyncTask<Void, Void, Void>() {
|
| + @VisibleForTesting
|
| + public static void getURL(final Context context, final String webappId,
|
| + final FetchCallback<String> callback) {
|
| + new AsyncTask<Void, Void, String>() {
|
| @Override
|
| - protected final Void doInBackground(Void... nothing) {
|
| - new WebappDataStorage(context.getApplicationContext(), webappId).setScope(scope);
|
| - return null;
|
| + protected final String doInBackground(Void... nothing) {
|
| + return new WebappDataStorage(context.getApplicationContext(), webappId).getURL();
|
| + }
|
| +
|
| + @Override
|
| + protected final void onPostExecute(String url) {
|
| + assert callback != null;
|
| + callback.onDataRetrieved(url);
|
| }
|
| }.execute();
|
| }
|
| @@ -147,18 +156,18 @@ public class WebappDataStorage {
|
| }
|
|
|
| /**
|
| - * Deletes the scope and sets last used time to 0 this web app in SharedPreferences.
|
| + * Deletes the URL and scope, and sets last used time to 0 in SharedPreferences.
|
| * This does not remove the stored splash screen image (if any) for the app.
|
| * @param context The context to read the SharedPreferences file.
|
| - * @param webappId The ID of the web app being deleted.
|
| + * @param webappId The ID of the web app for which history is being cleared.
|
| */
|
| static void clearHistory(final Context context, final String webappId) {
|
| // The last used time is set to 0 to ensure that a valid value is always present.
|
| - // If the webapp is not launched prior to the next cleanup, then its remaining data will be
|
| + // If the web app is not launched prior to the next cleanup, then its remaining data will be
|
| // removed. Otherwise, the next launch will update the last used time.
|
| assert !ThreadUtils.runningOnUiThread();
|
| - openSharedPreferences(context, webappId)
|
| - .edit().putLong(KEY_LAST_USED, LAST_USED_UNSET).remove(KEY_SCOPE).apply();
|
| + openSharedPreferences(context, webappId).edit()
|
| + .putLong(KEY_LAST_USED, LAST_USED_UNSET).remove(KEY_URL).remove(KEY_SCOPE).apply();
|
| }
|
|
|
| /**
|
| @@ -175,14 +184,14 @@ public class WebappDataStorage {
|
| }
|
|
|
| protected WebappDataStorage(Context context, String webappId) {
|
| + mId = webappId;
|
| mPreferences = openSharedPreferences(context, webappId);
|
| }
|
|
|
| - /*
|
| - * Asynchronously retrieves the splash screen image associated with the
|
| - * current web app.
|
| + /**
|
| + * Asynchronously retrieves the splash screen image associated with the current web app.
|
| * @param callback Called when the splash screen image has been retrieved.
|
| - * May be null if no image was found.
|
| + * The bitmap result may be null if no image was found.
|
| */
|
| public void getSplashScreenImage(final FetchCallback<Bitmap> callback) {
|
| new AsyncTask<Void, Void, Bitmap>() {
|
| @@ -194,45 +203,131 @@ public class WebappDataStorage {
|
|
|
| @Override
|
| protected final void onPostExecute(Bitmap result) {
|
| + assert callback != null;
|
| callback.onDataRetrieved(result);
|
| }
|
| }.execute();
|
| }
|
|
|
| - /*
|
| + /**
|
| * Update the information associated with the web app with the specified data.
|
| * @param splashScreenImage The image which should be shown on the splash screen of the web app.
|
| */
|
| public void updateSplashScreenImage(final Bitmap splashScreenImage) {
|
| + // Use an AsyncTask as this method is invoked on the UI thread from the callbacks leading to
|
| + // ShortcutHelper.storeWebappSplashImage.
|
| new AsyncTask<Void, Void, Void>() {
|
| @Override
|
| protected final Void doInBackground(Void... nothing) {
|
| - mPreferences.edit()
|
| - .putString(KEY_SPLASH_ICON,
|
| - ShortcutHelper.encodeBitmapAsString(splashScreenImage))
|
| - .apply();
|
| + String bitmap = ShortcutHelper.encodeBitmapAsString(splashScreenImage);
|
| + mPreferences.edit().putString(KEY_SPLASH_ICON, bitmap).apply();
|
| return null;
|
| }
|
| }.execute();
|
| }
|
|
|
| /**
|
| - * Updates the scope stored in this object. Does nothing if there is already a scope stored.
|
| - * @param scope the scope to store.
|
| + * Creates and returns a web app launch intent from the data stored in this object. Must not be
|
| + * called on the UI thread as a Bitmap is decoded from a String (a potentially expensive
|
| + * operation).
|
| + * @return The web app launch intent.
|
| */
|
| - void setScope(String scope) {
|
| + public Intent createWebappLaunchIntent() {
|
| assert !ThreadUtils.runningOnUiThread();
|
| - if (mPreferences.getString(KEY_SCOPE, SCOPE_INVALID).equals(SCOPE_INVALID)) {
|
| - mPreferences.edit().putString(KEY_SCOPE, scope).apply();
|
| + // Assume that all of the data is invalid if the version isn't set, so return a null intent.
|
| + int version = mPreferences.getInt(KEY_VERSION, VERSION_INVALID);
|
| + if (version == VERSION_INVALID) return null;
|
| +
|
| + return ShortcutHelper.createWebappShortcutIntent(mId,
|
| + mPreferences.getString(KEY_ACTION, null),
|
| + mPreferences.getString(KEY_URL, null),
|
| + mPreferences.getString(KEY_SCOPE, null),
|
| + mPreferences.getString(KEY_NAME, null),
|
| + mPreferences.getString(KEY_SHORT_NAME, null),
|
| + ShortcutHelper.decodeBitmapFromString(
|
| + mPreferences.getString(KEY_ICON, null)), version,
|
| + mPreferences.getInt(KEY_ORIENTATION, ScreenOrientationValues.DEFAULT),
|
| + mPreferences.getLong(KEY_THEME_COLOR,
|
| + ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING),
|
| + mPreferences.getLong(KEY_BACKGROUND_COLOR,
|
| + ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING),
|
| + mPreferences.getBoolean(KEY_IS_ICON_GENERATED, false));
|
| + }
|
| +
|
| + /**
|
| + * Updates the data stored in this object to match that in the supplied intent.
|
| + * @param shortcutIntent The intent to pull web app data from.
|
| + */
|
| + public void updateFromShortcutIntent(Intent shortcutIntent) {
|
| + if (shortcutIntent == null) return;
|
| +
|
| + SharedPreferences.Editor editor = mPreferences.edit();
|
| + boolean updated = false;
|
| +
|
| + // The URL and scope may have been deleted by the user clearing their history. Check whether
|
| + // they are present, and update if necessary.
|
| + String url = mPreferences.getString(KEY_URL, URL_INVALID);
|
| + if (url.equals(URL_INVALID)) {
|
| + url = IntentUtils.safeGetStringExtra(shortcutIntent, ShortcutHelper.EXTRA_URL);
|
| + editor.putString(KEY_URL, url);
|
| + updated = true;
|
| + }
|
| +
|
| + if (mPreferences.getString(KEY_SCOPE, URL_INVALID).equals(URL_INVALID)) {
|
| + String scope = IntentUtils.safeGetStringExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_SCOPE);
|
| + if (scope == null) {
|
| + scope = ShortcutHelper.getScopeFromUrl(url);
|
| + }
|
| + editor.putString(KEY_SCOPE, scope);
|
| + updated = true;
|
| }
|
| +
|
| + // For all other fields, assume that if the version key is present and equal to
|
| + // ShortcutHelper.WEBAPP_SHORTCUT_VERSION, then all fields are present and do not need to be
|
| + // updated. All fields except for the last used time, scope, and URL are either set or
|
| + // cleared together.
|
| + if (mPreferences.getInt(KEY_VERSION, VERSION_INVALID)
|
| + != ShortcutHelper.WEBAPP_SHORTCUT_VERSION) {
|
| + editor.putString(KEY_NAME, IntentUtils.safeGetStringExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_NAME));
|
| + editor.putString(KEY_SHORT_NAME, IntentUtils.safeGetStringExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_SHORT_NAME));
|
| + editor.putString(KEY_ICON, IntentUtils.safeGetStringExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_ICON));
|
| + editor.putInt(KEY_VERSION, ShortcutHelper.WEBAPP_SHORTCUT_VERSION);
|
| + editor.putInt(KEY_ORIENTATION, IntentUtils.safeGetIntExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_ORIENTATION,
|
| + ScreenOrientationValues.DEFAULT));
|
| + editor.putLong(KEY_THEME_COLOR, IntentUtils.safeGetLongExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_THEME_COLOR,
|
| + ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING));
|
| + editor.putLong(KEY_BACKGROUND_COLOR, IntentUtils.safeGetLongExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_BACKGROUND_COLOR,
|
| + ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING));
|
| + editor.putBoolean(KEY_IS_ICON_GENERATED, IntentUtils.safeGetBooleanExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_IS_ICON_GENERATED, false));
|
| + editor.putString(KEY_ACTION, shortcutIntent.getAction());
|
| + editor.putInt(KEY_SOURCE, IntentUtils.safeGetIntExtra(
|
| + shortcutIntent, ShortcutHelper.EXTRA_SOURCE,
|
| + ShortcutSource.UNKNOWN));
|
| + updated = true;
|
| + }
|
| + if (updated) editor.apply();
|
| }
|
|
|
| /**
|
| - * Returns the scope stored in this object, or "" if it is not stored.
|
| + * Returns the scope stored in this object, or URL_INVALID if it is not stored.
|
| */
|
| String getScope() {
|
| - assert !ThreadUtils.runningOnUiThread();
|
| - return mPreferences.getString(KEY_SCOPE, SCOPE_INVALID);
|
| + return mPreferences.getString(KEY_SCOPE, URL_INVALID);
|
| + }
|
| +
|
| + /**
|
| + * Returns the URL stored in this object, or URL_INVALID if it is not stored.
|
| + */
|
| + String getURL() {
|
| + return mPreferences.getString(KEY_URL, URL_INVALID);
|
| }
|
|
|
| /**
|
| @@ -240,7 +335,6 @@ public class WebappDataStorage {
|
| * @param lastUsedTime the new last used time.
|
| */
|
| void updateLastUsedTime() {
|
| - assert !ThreadUtils.runningOnUiThread();
|
| mPreferences.edit().putLong(KEY_LAST_USED, System.currentTimeMillis()).apply();
|
| }
|
|
|
| @@ -248,7 +342,6 @@ public class WebappDataStorage {
|
| * Returns the last used time of this object, or -1 if it is not stored.
|
| */
|
| long getLastUsedTime() {
|
| - assert !ThreadUtils.runningOnUiThread();
|
| return mPreferences.getLong(KEY_LAST_USED, LAST_USED_INVALID);
|
| }
|
|
|
|
|