Commit 6474dff1 authored by David Maunder's avatar David Maunder Committed by Commit Bot

Add ability to re-acquire ShoppingPersistedTabData after timeout

We want to be able to identify and display price updates
in the Tab Grid Switcher. This change to the infrastructure
will provide us with the capability to do that.

Bug: 1139459
Change-Id: I7588b377bc1fd1532a67373835c4204201723fdf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2481010Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Commit-Queue: David Maunder <davidjm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821019}
parent 4da6ac01
...@@ -1023,6 +1023,7 @@ android_library("chrome_test_java") { ...@@ -1023,6 +1023,7 @@ android_library("chrome_test_java") {
"//chrome/browser/contextmenu:java", "//chrome/browser/contextmenu:java",
"//chrome/browser/device:java", "//chrome/browser/device:java",
"//chrome/browser/download/android:java", "//chrome/browser/download/android:java",
"//chrome/browser/endpoint_fetcher:java",
"//chrome/browser/engagement/android:java", "//chrome/browser/engagement/android:java",
"//chrome/browser/enterprise/util:java", "//chrome/browser/enterprise/util:java",
"//chrome/browser/flags:java", "//chrome/browser/flags:java",
......
...@@ -11,6 +11,7 @@ include_rules = [ ...@@ -11,6 +11,7 @@ include_rules = [
"+chrome/browser/tabmodel/android/java", "+chrome/browser/tabmodel/android/java",
"+chrome/browser/tabpersistence/android/java", "+chrome/browser/tabpersistence/android/java",
"+chrome/browser/thumbnail/generator/android/java", "+chrome/browser/thumbnail/generator/android/java",
"+chrome/browser/endpoint_fetcher",
"+chrome/browser/ui/android/appmenu", "+chrome/browser/ui/android/appmenu",
"-chrome/browser/ui/android/appmenu/internal", "-chrome/browser/ui/android/appmenu/internal",
"+chrome/browser/ui/messages/android/java", "+chrome/browser/ui/messages/android/java",
......
include_rules = [
"+chrome/browser/endpoint_fetcher",
]
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.tab.state; package org.chromium.chrome.browser.tab.state;
import android.os.SystemClock;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
...@@ -36,9 +38,11 @@ import java.util.Map; ...@@ -36,9 +38,11 @@ import java.util.Map;
public abstract class PersistedTabData implements UserData { public abstract class PersistedTabData implements UserData {
private static final String TAG = "PTD"; private static final String TAG = "PTD";
private static final Map<String, List<Callback>> sCachedCallbacks = new HashMap<>(); private static final Map<String, List<Callback>> sCachedCallbacks = new HashMap<>();
private static final long NEEDS_UPDATE_DISABLED = Long.MAX_VALUE;
protected final Tab mTab; protected final Tab mTab;
private final PersistedTabDataStorage mPersistedTabDataStorage; private final PersistedTabDataStorage mPersistedTabDataStorage;
private final String mPersistedTabDataId; private final String mPersistedTabDataId;
private long mLastUpdatedMs;
/** /**
* @param tab {@link Tab} {@link PersistedTabData} is being stored for * @param tab {@link Tab} {@link PersistedTabData} is being stored for
...@@ -102,8 +106,19 @@ public abstract class PersistedTabData implements UserData { ...@@ -102,8 +106,19 @@ public abstract class PersistedTabData implements UserData {
// TODO(crbug.com/1059602) cache callbacks // TODO(crbug.com/1059602) cache callbacks
T persistedTabDataFromTab = getUserData(tab, clazz); T persistedTabDataFromTab = getUserData(tab, clazz);
if (persistedTabDataFromTab != null) { if (persistedTabDataFromTab != null) {
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, if (persistedTabDataFromTab.needsUpdate()) {
() -> { callback.onResult(persistedTabDataFromTab); }); supplier.onAvailable((tabData) -> {
updateLastUpdatedMs(tabData);
if (tabData != null) {
setUserData(tab, clazz, tabData);
}
PostTask.runOrPostTask(
UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(tabData); });
});
} else {
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
() -> { callback.onResult(persistedTabDataFromTab); });
}
return; return;
} }
String key = String.format(Locale.ENGLISH, "%d-%s", tab.getId(), clazz.toString()); String key = String.format(Locale.ENGLISH, "%d-%s", tab.getId(), clazz.toString());
...@@ -113,16 +128,41 @@ public abstract class PersistedTabData implements UserData { ...@@ -113,16 +128,41 @@ public abstract class PersistedTabData implements UserData {
PersistedTabDataConfiguration config = PersistedTabDataConfiguration config =
PersistedTabDataConfiguration.get(clazz, tab.isIncognito()); PersistedTabDataConfiguration.get(clazz, tab.isIncognito());
config.storage.restore(tab.getId(), config.id, (data) -> { config.storage.restore(tab.getId(), config.id, (data) -> {
T persistedTabData;
if (data == null) { if (data == null) {
supplier.onAvailable((ptd) -> { onPersistedTabDataResult(ptd, tab, clazz, key); }); supplier.onAvailable((tabData) -> {
updateLastUpdatedMs(tabData);
onPersistedTabDataResult(tabData, tab, clazz, key);
});
} else { } else {
persistedTabData = factory.create(data, config.storage, config.id); T persistedTabDataFromStorage = factory.create(data, config.storage, config.id);
onPersistedTabDataResult(persistedTabData, tab, clazz, key); if (persistedTabDataFromStorage.needsUpdate()) {
supplier.onAvailable((tabData) -> {
updateLastUpdatedMs(tabData);
onPersistedTabDataResult(tabData, tab, clazz, key);
});
} else {
onPersistedTabDataResult(persistedTabDataFromStorage, tab, clazz, key);
}
} }
}); });
} }
private static void updateLastUpdatedMs(PersistedTabData persistedTabData) {
if (persistedTabData != null) {
persistedTabData.setLastUpdatedMs(SystemClock.uptimeMillis());
}
}
/**
* @return if the {@link PersistedTabData} should be refetched.
*/
protected boolean needsUpdate() {
if (getTimeToLiveMs() == NEEDS_UPDATE_DISABLED) {
return false;
}
return mLastUpdatedMs + getTimeToLiveMs() < SystemClock.uptimeMillis();
}
private static <T extends PersistedTabData> void onPersistedTabDataResult( private static <T extends PersistedTabData> void onPersistedTabDataResult(
T persistedTabData, Tab tab, Class<T> clazz, String key) { T persistedTabData, Tab tab, Class<T> clazz, String key) {
if (persistedTabData != null) { if (persistedTabData != null) {
...@@ -146,14 +186,22 @@ public abstract class PersistedTabData implements UserData { ...@@ -146,14 +186,22 @@ public abstract class PersistedTabData implements UserData {
*/ */
protected static <T extends PersistedTabData> T from( protected static <T extends PersistedTabData> T from(
Tab tab, Class<T> userDataKey, Supplier<T> supplier) { Tab tab, Class<T> userDataKey, Supplier<T> supplier) {
UserDataHost host = tab.getUserDataHost(); T persistedTabData = from(tab, userDataKey);
T persistedTabData = host.getUserData(userDataKey);
if (persistedTabData == null) { if (persistedTabData == null) {
persistedTabData = host.setUserData(userDataKey, supplier.get()); persistedTabData = tab.getUserDataHost().setUserData(userDataKey, supplier.get());
} }
return persistedTabData; return persistedTabData;
} }
/**
* Acquire {@link PersistedTabData} from a {@link Tab} using a {@link UserData} key
* @param tab the {@link PersistedTabData} will be acquired from
* @param userDataKey the {@link UserData} object to be acquired from the {@link Tab}
*/
protected static <T extends PersistedTabData> T from(Tab tab, Class<T> userDataKey) {
return tab.getUserDataHost().getUserData(userDataKey);
}
private static <T extends PersistedTabData> void addCallback(String key, Callback<T> callback) { private static <T extends PersistedTabData> void addCallback(String key, Callback<T> callback) {
if (!sCachedCallbacks.containsKey(key)) { if (!sCachedCallbacks.containsKey(key)) {
sCachedCallbacks.put(key, new LinkedList<>()); sCachedCallbacks.put(key, new LinkedList<>());
...@@ -242,4 +290,29 @@ public abstract class PersistedTabData implements UserData { ...@@ -242,4 +290,29 @@ public abstract class PersistedTabData implements UserData {
* @return unique tag for logging in Uma * @return unique tag for logging in Uma
*/ */
public abstract String getUmaTag(); public abstract String getUmaTag();
/**
* @return length of time before data should be refetched from endpoint
* The default value is NEEDS_UPDATE_DISABLED (Long.MAX_VALUE) indicating
* the PersistedTabData will never be refetched. Subclasses can override
* this value if they need to make use of the time to live functionality.
*/
public long getTimeToLiveMs() {
return NEEDS_UPDATE_DISABLED;
}
/**
* Set last time the {@link PersistedTabData} was updated
* @param lastUpdatedMs time last updated in milliseconds
*/
protected void setLastUpdatedMs(long lastUpdatedMs) {
mLastUpdatedMs = lastUpdatedMs;
}
/**
* @return time the {@link PersistedTabDAta} was last updated in milliseconds
*/
protected long getLastUpdatedMs() {
return mLastUpdatedMs;
}
} }
...@@ -4,7 +4,11 @@ ...@@ -4,7 +4,11 @@
package org.chromium.chrome.browser.tab.state; package org.chromium.chrome.browser.tab.state;
import android.os.SystemClock;
import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
...@@ -21,6 +25,7 @@ import org.chromium.chrome.browser.tab.Tab; ...@@ -21,6 +25,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.proto.ShoppingPersistedTabData.ShoppingPersistedTabDataProto; import org.chromium.chrome.browser.tab.proto.ShoppingPersistedTabData.ShoppingPersistedTabDataProto;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.TimeUnit;
/** /**
* {@link PersistedTabData} for Shopping related websites * {@link PersistedTabData} for Shopping related websites
...@@ -43,7 +48,18 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -43,7 +48,18 @@ public class ShoppingPersistedTabData extends PersistedTabData {
private static final String TYPE_KEY = "type"; private static final String TYPE_KEY = "type";
private static final String SHOPPING_ID = "SHOPPING"; private static final String SHOPPING_ID = "SHOPPING";
private String mPriceString; private static final Class<ShoppingPersistedTabData> USER_DATA_KEY =
ShoppingPersistedTabData.class;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public static final long ONE_HOUR_MS = TimeUnit.HOURS.toMillis(1);
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public static final long NO_TRANSITIONS_OCCURRED = -1;
private long mTimeToLiveMs = ONE_HOUR_MS;
private String mPriceString = "";
private String mPreviousPriceString = "";
public long mLastPriceChangeTimeMs = NO_TRANSITIONS_OCCURRED;
protected ShoppingPersistedTabData(Tab tab) { protected ShoppingPersistedTabData(Tab tab) {
super(tab, super(tab,
...@@ -70,11 +86,14 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -70,11 +86,14 @@ public class ShoppingPersistedTabData extends PersistedTabData {
new OneshotSupplierImpl<ShoppingPersistedTabData>() { new OneshotSupplierImpl<ShoppingPersistedTabData>() {
@Override @Override
public void onAvailable(Callback<ShoppingPersistedTabData> supplierCallback) { public void onAvailable(Callback<ShoppingPersistedTabData> supplierCallback) {
ShoppingPersistedTabData previousShoppingPersistedTabData =
PersistedTabData.from(tab, USER_DATA_KEY);
EndpointFetcher.fetchUsingOAuth( EndpointFetcher.fetchUsingOAuth(
(endpointResponse) (endpointResponse)
-> { -> {
supplierCallback.onResult( supplierCallback.onResult(
build(tab, endpointResponse.getResponseString())); build(tab, endpointResponse.getResponseString(),
previousShoppingPersistedTabData));
}, },
Profile.getLastUsedRegularProfile(), OAUTH_NAME, Profile.getLastUsedRegularProfile(), OAUTH_NAME,
String.format(Locale.US, ENDPOINT, tab.getUrlString()), String.format(Locale.US, ENDPOINT, tab.getUrlString()),
...@@ -84,7 +103,8 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -84,7 +103,8 @@ public class ShoppingPersistedTabData extends PersistedTabData {
ShoppingPersistedTabData.class, callback); ShoppingPersistedTabData.class, callback);
} }
private static ShoppingPersistedTabData build(Tab tab, String responseString) { private static ShoppingPersistedTabData build(Tab tab, String responseString,
ShoppingPersistedTabData previousShoppingPersistedTabData) {
ShoppingPersistedTabData res = new ShoppingPersistedTabData(tab); ShoppingPersistedTabData res = new ShoppingPersistedTabData(tab);
try { try {
JSONObject jsonObject = new JSONObject(responseString); JSONObject jsonObject = new JSONObject(responseString);
...@@ -94,7 +114,8 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -94,7 +114,8 @@ public class ShoppingPersistedTabData extends PersistedTabData {
// TODO(crbug.com/1130068) support all currencies // TODO(crbug.com/1130068) support all currencies
if (SHOPPING_ID.equals(representation.getString(TYPE_KEY))) { if (SHOPPING_ID.equals(representation.getString(TYPE_KEY))) {
res.setPriceString( res.setPriceString(
String.format(Locale.US, "$%.2f", representation.getDouble(PRICE_KEY))); String.format(Locale.US, "$%.2f", representation.getDouble(PRICE_KEY)),
previousShoppingPersistedTabData);
break; break;
} }
} }
...@@ -112,9 +133,21 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -112,9 +133,21 @@ public class ShoppingPersistedTabData extends PersistedTabData {
/** /**
* Set the price string * Set the price string
* @param priceString a string representing the price of the shopping offer * @param priceString a string representing the price of the shopping offer
* @param previousShoppingPersistedTabData {@link ShoppingPersistedTabData} from previous fetch
*/ */
protected void setPriceString(String priceString) { protected void setPriceString(
String priceString, ShoppingPersistedTabData previousShoppingPersistedTabData) {
mPriceString = priceString; mPriceString = priceString;
// Detect price transition
if (previousShoppingPersistedTabData != null && !TextUtils.isEmpty(priceString)
&& !TextUtils.isEmpty(previousShoppingPersistedTabData.getPriceString())
&& !priceString.equals(previousShoppingPersistedTabData.getPriceString())) {
mPreviousPriceString = previousShoppingPersistedTabData.getPriceString();
mLastPriceChangeTimeMs = SystemClock.uptimeMillis();
} else if (previousShoppingPersistedTabData != null) {
mPreviousPriceString = previousShoppingPersistedTabData.getPreviousPriceString();
mLastPriceChangeTimeMs = previousShoppingPersistedTabData.getLastPriceChangeTimeMs();
}
save(); save();
} }
...@@ -128,10 +161,22 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -128,10 +161,22 @@ public class ShoppingPersistedTabData extends PersistedTabData {
return mPriceString; return mPriceString;
} }
/**
* @return the price string of the {@link ShoppingPersistedTabDta}
* before the refetch occurred because of a timeout. This enables
* the consumer to determine if the price changed during the fetch.
*/
public String getPreviousPriceString() {
return mPreviousPriceString;
}
@Override @Override
public byte[] serialize() { public byte[] serialize() {
return ShoppingPersistedTabDataProto.newBuilder() return ShoppingPersistedTabDataProto.newBuilder()
.setPriceString(mPriceString) .setPriceString(mPriceString)
.setPreviousPriceString(mPreviousPriceString)
.setLastUpdatedMs(getLastUpdatedMs())
.setLastPriceChangeTimeMs(mLastPriceChangeTimeMs)
.build() .build()
.toByteArray(); .toByteArray();
} }
...@@ -143,6 +188,9 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -143,6 +188,9 @@ public class ShoppingPersistedTabData extends PersistedTabData {
ShoppingPersistedTabDataProto shoppingPersistedTabDataProto = ShoppingPersistedTabDataProto shoppingPersistedTabDataProto =
ShoppingPersistedTabDataProto.parseFrom(bytes); ShoppingPersistedTabDataProto.parseFrom(bytes);
mPriceString = shoppingPersistedTabDataProto.getPriceString(); mPriceString = shoppingPersistedTabDataProto.getPriceString();
mPreviousPriceString = shoppingPersistedTabDataProto.getPreviousPriceString();
setLastUpdatedMs(shoppingPersistedTabDataProto.getLastUpdatedMs());
mLastPriceChangeTimeMs = shoppingPersistedTabDataProto.getLastPriceChangeTimeMs();
return true; return true;
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
Log.e(TAG, Log.e(TAG,
...@@ -161,4 +209,19 @@ public class ShoppingPersistedTabData extends PersistedTabData { ...@@ -161,4 +209,19 @@ public class ShoppingPersistedTabData extends PersistedTabData {
public String getUmaTag() { public String getUmaTag() {
return "SPTD"; return "SPTD";
} }
@Override
public long getTimeToLiveMs() {
return mTimeToLiveMs;
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public void setTimeToLiveMs(long timeToLiveMs) {
mTimeToLiveMs = timeToLiveMs;
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public long getLastPriceChangeTimeMs() {
return mLastPriceChangeTimeMs;
}
} }
\ No newline at end of file
...@@ -9,6 +9,17 @@ package org.chromium.chrome.browser.tab.proto; ...@@ -9,6 +9,17 @@ package org.chromium.chrome.browser.tab.proto;
option java_package = "org.chromium.chrome.browser.tab.proto"; option java_package = "org.chromium.chrome.browser.tab.proto";
message ShoppingPersistedTabDataProto { message ShoppingPersistedTabDataProto {
// String representing the price of the offer // String representing the price of the offer.
optional string price_string = 1; optional string price_string = 1;
// String representing previous price of the offer.
optional string previous_price_string = 2;
// Time the ShoppingPersistedTabData was fetched
// measured in the number of seconds since the epoch.
optional int64 last_updated_ms = 3;
// Time the price changed measured in the number of
// seconds since the epoch.
optional int64 last_price_change_time_ms = 4;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment