Files
cromite/build/patches/Bromite-auto-updater.patch
2024-03-17 19:41:20 +01:00

1950 lines
89 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
From: uazo <uazo@users.noreply.github.com>
Date: Thu, 7 Oct 2021 14:27:12 +0000
Subject: Bromite auto updater
Enable checking for new versions, with notifications and proxy support.
Restore InlineUpdateFlow feature.
Some parts authored by csagan5.
License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
---
.../java/templates/BuildConfig.template | 2 +
build/config/android/rules.gni | 3 +
chrome/android/chrome_java_sources.gni | 3 +
.../java/res/xml/about_chrome_preferences.xml | 5 +
.../about_settings/AboutChromeSettings.java | 28 +-
.../chrome/browser/omaha/OmahaBase.java | 57 +++-
.../chrome/browser/omaha/UpdateConfigs.java | 27 +-
.../browser/omaha/UpdateMenuItemHelper.java | 82 +++++-
.../browser/omaha/UpdateStatusProvider.java | 165 ++++++++---
.../browser/omaha/VersionNumberGetter.java | 3 +-
.../inline/BromiteInlineUpdateController.java | 272 ++++++++++++++++++
.../omaha/inline/InlineUpdateController.java | 51 ++++
.../inline/InlineUpdateControllerFactory.java | 21 ++
chrome/browser/BUILD.gn | 2 +
chrome/browser/endpoint_fetcher/BUILD.gn | 32 +++
chrome/browser/endpoint_fetcher/DEPS | 3 +
chrome/browser/endpoint_fetcher/OWNERS | 1 +
.../endpoint_fetcher_android.cc | 184 ++++++++++++
.../endpoint_fetcher/EndpointFetcher.java | 165 +++++++++++
.../EndpointHeaderResponse.java | 31 ++
.../endpoint_fetcher/EndpointResponse.java | 30 ++
.../flags/android/chrome_feature_list.cc | 1 +
.../browser/flags/ChromeFeatureList.java | 1 +
.../strings/android_chrome_strings.grd | 23 +-
.../endpoint_fetcher/endpoint_fetcher.cc | 101 ++++++-
.../endpoint_fetcher/endpoint_fetcher.h | 23 +-
.../Bromite-auto-updater.inc | 3 +
.../Bromite-auto-updater.inc | 1 +
28 files changed, 1268 insertions(+), 52 deletions(-)
create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/BromiteInlineUpdateController.java
create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateController.java
create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateControllerFactory.java
create mode 100644 chrome/browser/endpoint_fetcher/BUILD.gn
create mode 100644 chrome/browser/endpoint_fetcher/DEPS
create mode 100644 chrome/browser/endpoint_fetcher/OWNERS
create mode 100644 chrome/browser/endpoint_fetcher/endpoint_fetcher_android.cc
create mode 100644 chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointFetcher.java
create mode 100644 chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointHeaderResponse.java
create mode 100644 chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointResponse.java
create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Bromite-auto-updater.inc
create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Bromite-auto-updater.inc
diff --git a/build/android/java/templates/BuildConfig.template b/build/android/java/templates/BuildConfig.template
--- a/build/android/java/templates/BuildConfig.template
+++ b/build/android/java/templates/BuildConfig.template
@@ -87,6 +87,8 @@ public class BuildConfig {
public static MAYBE_FINAL boolean IS_FOR_TEST MAYBE_FALSE;
#endif
+ public static MAYBE_FINAL String BUILD_TARGET_CPU = _BUILD_TARGET_CPU;
+
#if defined(_WRITE_CLANG_PROFILING_DATA)
public static MAYBE_FINAL boolean WRITE_CLANG_PROFILING_DATA = true;
#else
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1905,6 +1905,9 @@ if (enable_java_templates && is_android) {
sources = [ "//build/android/java/templates/BuildConfig.template" ]
defines = []
+ # add arch to org.chromium.build.BuildConfig
+ defines += [ "_BUILD_TARGET_CPU=\"${target_cpu}\"" ]
+
# Set these even when !use_final_fields so that they have correct default
# values within robolectric_binary(), which ignores jar_excluded_patterns.
if ((defined(invoker.assertions_implicitly_enabled) &&
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -883,6 +883,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java",
"java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java",
"java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java",
+ "java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateController.java",
+ "java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateControllerFactory.java",
+ "java/src/org/chromium/chrome/browser/omaha/inline/BromiteInlineUpdateController.java",
"java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java",
"java/src/org/chromium/chrome/browser/omaha/metrics/TrackingProvider.java",
"java/src/org/chromium/chrome/browser/omaha/metrics/UpdateSuccessMetrics.java",
diff --git a/chrome/android/java/res/xml/about_chrome_preferences.xml b/chrome/android/java/res/xml/about_chrome_preferences.xml
--- a/chrome/android/java/res/xml/about_chrome_preferences.xml
+++ b/chrome/android/java/res/xml/about_chrome_preferences.xml
@@ -9,6 +9,11 @@ found in the LICENSE file.
<org.chromium.chrome.browser.about_settings.LongClickCopySummaryPreference
android:key="application_version"
android:title="@string/application_version_title" />
+ <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+ android:key="allow_inline_update"
+ android:title="@string/allow_inline_update_title"
+ android:summary="@string/allow_inline_update_summary"
+ android:defaultValue="false" />
<org.chromium.chrome.browser.about_settings.AboutChromePreferenceOSVersion
android:key="os_version"
android:title="@string/os_version_title" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/about_settings/AboutChromeSettings.java
@@ -21,12 +21,18 @@ import org.chromium.ui.widget.Toast;
import java.util.Calendar;
+import android.content.SharedPreferences;
+import org.chromium.chrome.browser.omaha.OmahaBase;
+import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
+
/** Settings fragment that displays information about Chrome. */
public class AboutChromeSettings extends PreferenceFragmentCompat
- implements Preference.OnPreferenceClickListener {
+ implements Preference.OnPreferenceClickListener,
+ Preference.OnPreferenceChangeListener {
private static final int TAPS_FOR_DEVELOPER_SETTINGS = 7;
private static final String PREF_APPLICATION_VERSION = "application_version";
+ private static final String PREF_ALLOW_INLINE_UPDATE = "allow_inline_update"; // switch preference
private static final String PREF_OS_VERSION = "os_version";
private static final String PREF_LEGAL_INFORMATION = "legal_information";
@@ -57,6 +63,13 @@ public class AboutChromeSettings extends PreferenceFragmentCompat
p = findPreference(PREF_LEGAL_INFORMATION);
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
p.setSummary(getString(R.string.legal_information_summary, currentYear));
+
+ ChromeSwitchPreference allowInlineUpdate =
+ (ChromeSwitchPreference) findPreference(PREF_ALLOW_INLINE_UPDATE);
+ allowInlineUpdate.setChecked(
+ OmahaBase.getSharedPreferences()
+ .getBoolean(OmahaBase.PREF_ALLOW_INLINE_UPDATE, false));
+ allowInlineUpdate.setOnPreferenceChangeListener(this);
}
/**
@@ -121,4 +134,17 @@ public class AboutChromeSettings extends PreferenceFragmentCompat
}
return true;
}
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ String key = preference.getKey();
+ if (PREF_ALLOW_INLINE_UPDATE.equals(key)) {
+ SharedPreferences.Editor sharedPreferenceEditor = OmahaBase.getSharedPreferences().edit();
+ sharedPreferenceEditor.putBoolean(OmahaBase.PREF_ALLOW_INLINE_UPDATE, (boolean) newValue);
+ sharedPreferenceEditor.apply();
+
+ OmahaBase.resetUpdatePrefs();
+ }
+ return true;
+ }
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
@@ -33,6 +33,8 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
+import org.chromium.build.BuildConfig;
+
/**
* Keeps tabs on the current state of Chrome, tracking if and when a request should be sent to the
* Omaha Server.
@@ -103,7 +105,10 @@ public class OmahaBase {
static final String PREF_TIMESTAMP_FOR_NEW_REQUEST = "timestampForNewRequest";
static final String PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT = "timestampForNextPostAttempt";
static final String PREF_TIMESTAMP_OF_INSTALL = "timestampOfInstall";
- static final String PREF_TIMESTAMP_OF_REQUEST = "timestampOfRequest";
+ public static final String PREF_TIMESTAMP_OF_REQUEST = "timestampOfRequest";
+ public static final String PREF_LATEST_MODIFIED_VERSION = "latestModifiedVersion";
+ public static final String PREF_LATEST_UPSTREAM_VERSION = "latestUpstreamVersion";
+ public static final String PREF_ALLOW_INLINE_UPDATE = "allowInlineUpdate";
private static final int UNKNOWN_DATE = -2;
@@ -161,7 +166,8 @@ public class OmahaBase {
}
static boolean isDisabled() {
- return sDisabledForTesting;
+ // do not enable version control via Omaha Update Server
+ return true;
}
/**
@@ -599,6 +605,10 @@ public class OmahaBase {
/** Sends the request to the server and returns the response. */
static String sendRequestToServer(HttpURLConnection urlConnection, String request)
throws RequestFailureException {
+ if ((true)) {
+ throw new RequestFailureException("Requests to Omaha server are forbidden.",
+ RequestFailureException.ERROR_CONNECTIVITY);
+ }
try {
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
OutputStreamWriter writer = new OutputStreamWriter(out);
@@ -679,4 +689,47 @@ public class OmahaBase {
// updateStatus is only used for the on-demand check.
null);
}
+
+ public static boolean isNewVersionAvailableByVersion(VersionNumber latestVersion) {
+ VersionNumber mCurrentProductVersion = VersionNumber.fromString(VersionInfo.getProductVersion());
+ if (mCurrentProductVersion == null) {
+ Log.e(TAG, "BromiteUpdater: current product version is null");
+ return false;
+ }
+
+ Log.i(TAG, "BromiteUpdater: currentProductVersion=%s, latestVersion=%s",
+ mCurrentProductVersion.toString(), latestVersion.toString());
+
+ return mCurrentProductVersion.isSmallerThan(latestVersion);
+ }
+
+ public static void updateLastPushedTimeStamp(long timeMillis) {
+ SharedPreferences preferences = OmahaBase.getSharedPreferences();
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, timeMillis);
+ editor.apply();
+ }
+
+ public static void setLatestModifiedVersion(String version) {
+ SharedPreferences preferences = OmahaBase.getSharedPreferences();
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(OmahaBase.PREF_LATEST_MODIFIED_VERSION, version);
+ editor.apply();
+ }
+
+ public static void setLatestUpstreamVersion(String version) {
+ SharedPreferences preferences = OmahaBase.getSharedPreferences();
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(OmahaBase.PREF_LATEST_UPSTREAM_VERSION, version);
+ editor.apply();
+ }
+
+ public static void resetUpdatePrefs() {
+ SharedPreferences preferences = OmahaBase.getSharedPreferences();
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, 0);
+ editor.putString(OmahaBase.PREF_LATEST_MODIFIED_VERSION, "");
+ editor.putString(OmahaBase.PREF_LATEST_UPSTREAM_VERSION, "");
+ editor.apply();
+ }
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java
@@ -11,6 +11,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import org.chromium.base.CommandLine;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState;
import org.chromium.components.variations.VariationsAssociatedData;
@@ -36,10 +37,12 @@ public class UpdateConfigs {
private static final String UPDATE_AVAILABLE_SWITCH_VALUE = "update_available";
private static final String UNSUPPORTED_OS_VERSION_SWITCH_VALUE = "unsupported_os_version";
+ private static final long DEFAULT_UPDATE_NOTIFICATION_INTERVAL = 3 * DateUtils.DAY_IN_MILLIS;
private static final long DEFAULT_UPDATE_ATTRIBUTION_WINDOW_MS = 2 * DateUtils.DAY_IN_MILLIS;
/** Possible update flow configurations. */
- @IntDef({UpdateFlowConfiguration.NEVER_SHOW, UpdateFlowConfiguration.INTENT_ONLY})
+ @IntDef({UpdateFlowConfiguration.NEVER_SHOW, UpdateFlowConfiguration.INTENT_ONLY,
+ UpdateFlowConfiguration.INLINE_ONLY})
@Retention(RetentionPolicy.SOURCE)
public @interface UpdateFlowConfiguration {
/** Turns off all update indicators. */
@@ -49,6 +52,12 @@ public class UpdateConfigs {
* Requires Omaha to say an update is available, and only ever Intents out to Play Store.
*/
int INTENT_ONLY = 2;
+
+ /**
+ * Inline updates that contact Bromite official GitHub repository to say whether an update is available.
+ * Only ever uses the inline update flow.
+ */
+ int INLINE_ONLY = 3;
}
/**
@@ -124,6 +133,13 @@ public class UpdateConfigs {
return DEFAULT_UPDATE_ATTRIBUTION_WINDOW_MS;
}
+ /**
+ * @return A time interval for scheduling update notification. Unit: mills.
+ */
+ public static long getUpdateNotificationInterval() {
+ return DEFAULT_UPDATE_NOTIFICATION_INTERVAL;
+ }
+
/**
* Gets a String VariationsAssociatedData parameter. Also checks for a command-line switch
* with the same name, for easy local testing.
@@ -137,4 +153,13 @@ public class UpdateConfigs {
}
return value;
}
+
+ @UpdateFlowConfiguration
+ public static int getConfiguration() {
+ if (!ChromeFeatureList.isEnabled(ChromeFeatureList.INLINE_UPDATE_FLOW)) {
+ // Always use the the old flow if the inline update flow feature is not enabled.
+ return UpdateFlowConfiguration.INLINE_ONLY;
+ }
+ return UpdateFlowConfiguration.NEVER_SHOW;
+ }
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
@@ -12,6 +12,7 @@ import android.view.Choreographer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import org.chromium.base.BuildInfo;
import org.chromium.base.Callback;
@@ -146,8 +147,21 @@ public class UpdateMenuItemHelper {
Log.e(TAG, "Failed to launch Activity for: %s", mStatus.updateUrl);
}
break;
+ case UpdateState.VULNERABLE_VERSION:
+ // Intentional fall through.
+ case UpdateState.INLINE_UPDATE_AVAILABLE:
+ UpdateStatusProvider.getInstance().startInlineUpdate(activity);
+ break;
+ case UpdateState.INLINE_UPDATE_READY:
+ UpdateStatusProvider.getInstance().finishInlineUpdate();
+ break;
+ case UpdateState.INLINE_UPDATE_FAILED:
+ UpdateStatusProvider.getInstance().retryInlineUpdate(activity);
+ break;
case UpdateState.UNSUPPORTED_OS_VERSION:
// Intentional fall through.
+ case UpdateState.INLINE_UPDATE_DOWNLOADING:
+ // Intentional fall through.
default:
return;
}
@@ -196,7 +210,7 @@ public class UpdateMenuItemHelper {
mMenuUiState = new MenuUiState();
switch (mStatus.updateState) {
- case UpdateState.UPDATE_AVAILABLE:
+ case UpdateState.UPDATE_AVAILABLE: // this is not used in Bromite
// The badge is hidden if the update menu item has been clicked until there is an
// even newer version of Chrome available.
showBadge |=
@@ -255,6 +269,72 @@ public class UpdateMenuItemHelper {
mMenuUiState.itemState.icon = R.drawable.ic_error_24dp_filled;
mMenuUiState.itemState.enabled = false;
break;
+ case UpdateState.VULNERABLE_VERSION:
+ // Intentional fall through.
+ case UpdateState.INLINE_UPDATE_AVAILABLE:
+ // The badge is hidden if the update menu item has been clicked until there is an
+ // even newer version of Chrome available.
+ @StringRes int defaultUpdateSummary = R.string.menu_update_summary_default;
+ if (mStatus.updateState == UpdateState.VULNERABLE_VERSION) {
+ // always show badge in case of vulnerable version
+ showBadge = true;
+ mMenuUiState.buttonState = new MenuButtonState();
+ mMenuUiState.buttonState.menuContentDescription = R.string.accessibility_toolbar_btn_menu_update;
+ mMenuUiState.buttonState.darkBadgeIcon =
+ R.drawable.ic_error_grey800_24dp_filled;
+ mMenuUiState.buttonState.lightBadgeIcon = R.drawable.ic_error_white_24dp_filled;
+ mMenuUiState.buttonState.adaptiveBadgeIcon = R.drawable.ic_error_24dp_filled;
+ defaultUpdateSummary = R.string.menu_update_summary_vulnerable;
+ } else {
+ showBadge |= !TextUtils.equals(
+ getPrefService().getString(
+ Pref.LATEST_VERSION_WHEN_CLICKED_UPDATE_MENU_ITEM),
+ mStatus.latestUnsupportedVersion);
+ if (showBadge) {
+ mMenuUiState.buttonState = new MenuButtonState();
+ mMenuUiState.buttonState.menuContentDescription = R.string.accessibility_toolbar_btn_menu_update;
+ mMenuUiState.buttonState.darkBadgeIcon = R.drawable.badge_update_dark;
+ mMenuUiState.buttonState.lightBadgeIcon = R.drawable.badge_update_light;
+ mMenuUiState.buttonState.adaptiveBadgeIcon = R.drawable.badge_update;
+ }
+ }
+
+ mMenuUiState.itemState = new MenuItemState();
+ mMenuUiState.itemState.title = R.string.menu_update;
+ mMenuUiState.itemState.titleColorId = R.color.default_text_color_blue_dark;
+ mMenuUiState.itemState.summary = UpdateConfigs.getCustomSummary();
+ if (TextUtils.isEmpty(mMenuUiState.itemState.summary)) {
+ mMenuUiState.itemState.summary =
+ resources.getString(defaultUpdateSummary);
+ }
+ mMenuUiState.itemState.icon = R.drawable.ic_history_googblue_24dp;
+ mMenuUiState.itemState.iconTintId = R.color.default_icon_color_blue_light;
+ mMenuUiState.itemState.enabled = true;
+ break;
+ case UpdateState.INLINE_UPDATE_DOWNLOADING:
+ mMenuUiState.itemState = new MenuItemState();
+ mMenuUiState.itemState.title = R.string.menu_inline_update_downloading;
+ mMenuUiState.itemState.titleColorId = R.color.default_text_color_secondary_dark;
+ break;
+ case UpdateState.INLINE_UPDATE_READY:
+ mMenuUiState.itemState = new MenuItemState();
+ mMenuUiState.itemState.title = R.string.menu_inline_update_ready;
+ mMenuUiState.itemState.titleColorId = R.color.default_text_color_blue_dark;
+ mMenuUiState.itemState.summary =
+ resources.getString(R.string.menu_inline_update_ready_summary);
+ mMenuUiState.itemState.icon = R.drawable.infobar_chrome;
+ mMenuUiState.itemState.iconTintId = R.color.default_icon_color_blue_light;
+ mMenuUiState.itemState.enabled = true;
+ break;
+ case UpdateState.INLINE_UPDATE_FAILED:
+ mMenuUiState.itemState = new MenuItemState();
+ mMenuUiState.itemState.title = R.string.menu_inline_update_failed;
+ mMenuUiState.itemState.titleColorId = R.color.default_text_color_blue_dark;
+ mMenuUiState.itemState.summary = resources.getString(R.string.try_again);
+ mMenuUiState.itemState.icon = R.drawable.ic_history_googblue_24dp;
+ mMenuUiState.itemState.iconTintId = R.color.default_icon_color_blue_light;
+ mMenuUiState.itemState.enabled = true;
+ break;
case UpdateState.NONE:
// Intentional fall through.
default:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.omaha;
+import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -15,7 +16,11 @@ import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ApplicationStatus.ActivityStateListener;
import org.chromium.base.BuildInfo;
import org.chromium.base.Callback;
import org.chromium.base.ObserverList;
@@ -25,6 +30,10 @@ import org.chromium.base.task.AsyncTask;
import org.chromium.base.task.AsyncTask.Status;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
+import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.omaha.inline.BromiteInlineUpdateController;
+import org.chromium.chrome.browser.omaha.inline.InlineUpdateController;
+import org.chromium.chrome.browser.omaha.inline.InlineUpdateControllerFactory;
import org.chromium.chrome.browser.omaha.metrics.UpdateSuccessMetrics;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
@@ -34,30 +43,37 @@ import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import org.chromium.base.Log;
+import android.content.SharedPreferences;
+import android.os.Build;
+import org.chromium.build.BuildConfig;
+
/**
* Provides the current update state for Chrome. This update state is asynchronously determined and
* can change as Chrome runs.
*
* For manually testing this functionality, see {@link UpdateConfigs}.
*/
-public class UpdateStatusProvider {
+public class UpdateStatusProvider implements ActivityStateListener {
/**
* Possible update states.
* Treat this as append only as it is used by UMA.
*/
- @IntDef({UpdateState.NONE, UpdateState.UPDATE_AVAILABLE, UpdateState.UNSUPPORTED_OS_VERSION})
+ @IntDef({UpdateState.NONE, UpdateState.UPDATE_AVAILABLE, UpdateState.UNSUPPORTED_OS_VERSION,
+ UpdateState.INLINE_UPDATE_AVAILABLE, UpdateState.INLINE_UPDATE_DOWNLOADING,
+ UpdateState.INLINE_UPDATE_READY, UpdateState.INLINE_UPDATE_FAILED, UpdateState.VULNERABLE_VERSION})
@Retention(RetentionPolicy.SOURCE)
public @interface UpdateState {
int NONE = 0;
int UPDATE_AVAILABLE = 1;
int UNSUPPORTED_OS_VERSION = 2;
- // Inline updates are deprecated.
- // int INLINE_UPDATE_AVAILABLE = 3;
- // int INLINE_UPDATE_DOWNLOADING = 4;
- // int INLINE_UPDATE_READY = 5;
- // int INLINE_UPDATE_FAILED = 6;
+ int INLINE_UPDATE_AVAILABLE = 3;
+ int INLINE_UPDATE_DOWNLOADING = 4;
+ int INLINE_UPDATE_READY = 5;
+ int INLINE_UPDATE_FAILED = 6;
+ int VULNERABLE_VERSION = 7;
- int NUM_ENTRIES = 7;
+ int NUM_ENTRIES = 8;
}
/** A set of properties that represent the current update state for Chrome. */
@@ -91,6 +107,12 @@ public class UpdateStatusProvider {
*/
private boolean mIsSimulated;
+ /**
+ * Whether or not we are currently trying to simulate an inline flow. Used to allow
+ * overriding Omaha update state, which usually supersedes inline update states.
+ */
+ private boolean mIsInlineSimulated;
+
public UpdateStatus() {}
UpdateStatus(UpdateStatus other) {
@@ -99,11 +121,13 @@ public class UpdateStatusProvider {
latestVersion = other.latestVersion;
latestUnsupportedVersion = other.latestUnsupportedVersion;
mIsSimulated = other.mIsSimulated;
+ mIsInlineSimulated = other.mIsInlineSimulated;
}
}
private final ObserverList<Callback<UpdateStatus>> mObservers = new ObserverList<>();
+ private final InlineUpdateController mInlineController;
private final UpdateQuery mOmahaQuery;
private final UpdateSuccessMetrics mMetrics;
private @Nullable UpdateStatus mStatus;
@@ -171,6 +195,30 @@ public class UpdateStatusProvider {
pingObservers();
}
+ /**
+ * Starts the inline update process, if possible.
+ * @param activity An {@link Activity} that will be used to interact with Play.
+ */
+ public void startInlineUpdate(Activity activity) {
+ if (mStatus == null || (mStatus.updateState != UpdateState.INLINE_UPDATE_AVAILABLE && mStatus.updateState != UpdateState.VULNERABLE_VERSION)) return;
+ mInlineController.startUpdate(activity);
+ }
+
+ /**
+ * Retries the inline update process, if possible.
+ * @param activity An {@link Activity} that will be used to interact with Play.
+ */
+ public void retryInlineUpdate(Activity activity) {
+ if (mStatus == null || (mStatus.updateState != UpdateState.INLINE_UPDATE_AVAILABLE && mStatus.updateState != UpdateState.VULNERABLE_VERSION)) return;
+ mInlineController.startUpdate(activity);
+ }
+
+ /** Finishes the inline update process, which may involve restarting the app. */
+ public void finishInlineUpdate() {
+ if (mStatus == null || mStatus.updateState != UpdateState.INLINE_UPDATE_READY) return;
+ mInlineController.completeUpdate();
+ }
+
/**
* Starts the intent update process, if possible
* @param context An {@link Context} that will be used to fire off the update intent.
@@ -178,12 +226,11 @@ public class UpdateStatusProvider {
* @return Whether or not the update intent was sent and had a valid handler.
*/
public boolean startIntentUpdate(Context context, boolean newTask) {
+ // currently not used in Bromite
if (mStatus == null || mStatus.updateState != UpdateState.UPDATE_AVAILABLE) return false;
if (TextUtils.isEmpty(mStatus.updateUrl)) return false;
try {
- mMetrics.startUpdate();
-
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mStatus.updateUrl));
if (newTask) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
@@ -194,9 +241,29 @@ public class UpdateStatusProvider {
return true;
}
+ // ApplicationStateListener implementation.
+ @Override
+ public void onActivityStateChange(Activity changedActivity, @ActivityState int newState) {
+ boolean hasActiveActivity = false;
+
+ for (Activity activity : ApplicationStatus.getRunningActivities()) {
+ if (activity == null || !(activity instanceof ChromeActivity)) continue;
+
+ hasActiveActivity |=
+ ApplicationStatus.getStateForActivity(activity) == ActivityState.RESUMED;
+ if (hasActiveActivity) break;
+ }
+
+ mInlineController.setEnabled(hasActiveActivity);
+ }
+
private UpdateStatusProvider() {
+ mInlineController = InlineUpdateControllerFactory.create(this::resolveStatus);
mOmahaQuery = new UpdateQuery(this::resolveStatus);
mMetrics = new UpdateSuccessMetrics();
+
+ // Note that as a singleton this class never unregisters.
+ ApplicationStatus.registerStateListenerForAllActivities(this);
}
private void pingObservers() {
@@ -204,19 +271,36 @@ public class UpdateStatusProvider {
}
private void resolveStatus() {
- if (mOmahaQuery.getStatus() != Status.FINISHED) {
+ if (mOmahaQuery.getStatus() != Status.FINISHED || mInlineController.getStatus() == null) {
return;
}
// We pull the Omaha result once as it will never change.
if (mStatus == null) mStatus = new UpdateStatus(mOmahaQuery.getResult());
- if (!mStatus.mIsSimulated) {
- mStatus.updateState = mOmahaQuery.getResult().updateState;
+ if (mStatus.mIsSimulated) { // used only during tests
+ if (mStatus.mIsInlineSimulated) {
+ @UpdateState
+ int inlineState = mInlineController.getStatus();
+ String updateUrl = mInlineController.getUpdateUrl();
+
+ if (inlineState == UpdateState.NONE) {
+ mStatus.updateState = mOmahaQuery.getResult().updateState;
+ } else {
+ mStatus.updateState = inlineState;
+ mStatus.updateUrl = updateUrl;
+ }
+ }
+ } else {
+ // used by Bromite to resolve update status
+ // ignores Omaha status
+ @UpdateState
+ int inlineState = mInlineController.getStatus();
+ mStatus.updateState = inlineState;
+ mStatus.updateUrl = mInlineController.getUpdateUrl();
}
if (!mRecordedInitialStatus) {
- mMetrics.analyzeFirstStatus();
mRecordedInitialStatus = true;
}
@@ -228,6 +312,7 @@ public class UpdateStatusProvider {
}
private static final class UpdateQuery extends AsyncTask<UpdateStatus> {
+ static final String TAG = "UpdateStatusProvider";
private final Runnable mCallback;
private @Nullable UpdateStatus mStatus;
@@ -244,7 +329,7 @@ public class UpdateStatusProvider {
protected UpdateStatus doInBackground() {
UpdateStatus testStatus = getTestStatus();
if (testStatus != null) return testStatus;
- return getRealStatus();
+ return getActualStatus();
}
@Override
@@ -262,6 +347,8 @@ public class UpdateStatusProvider {
status.mIsSimulated = true;
status.updateState = forcedUpdateState;
+ status.mIsInlineSimulated = forcedUpdateState == UpdateState.INLINE_UPDATE_AVAILABLE;
+
// Push custom configurations for certain update states.
switch (forcedUpdateState) {
case UpdateState.UPDATE_AVAILABLE:
@@ -279,31 +366,33 @@ public class UpdateStatusProvider {
return status;
}
- private UpdateStatus getRealStatus() {
+ private UpdateStatus getActualStatus() {
UpdateStatus status = new UpdateStatus();
- if (VersionNumberGetter.isNewerVersionAvailable()) {
- status.updateUrl = MarketURLGetter.getMarketUrl();
- status.latestVersion = VersionNumberGetter.getInstance().getLatestKnownVersion();
-
- boolean allowedToUpdate =
- checkForSufficientStorage()
- // Disable the version update check for automotive. See b/297925838.
- && !BuildInfo.getInstance().isAutomotive
- && PackageUtils.isPackageInstalled(
- GooglePlayServicesUtil.GOOGLE_PLAY_STORE_PACKAGE);
- status.updateState =
- allowedToUpdate ? UpdateState.UPDATE_AVAILABLE : UpdateState.NONE;
-
- ChromeSharedPreferences.getInstance()
- .removeKey(ChromePreferenceKeys.LATEST_UNSUPPORTED_VERSION);
- } else if (!VersionNumberGetter.isCurrentOsVersionSupported()) {
- status.updateState = UpdateState.UNSUPPORTED_OS_VERSION;
- status.latestUnsupportedVersion =
- ChromeSharedPreferences.getInstance()
- .readString(ChromePreferenceKeys.LATEST_UNSUPPORTED_VERSION, null);
- } else {
- status.updateState = UpdateState.NONE;
+ SharedPreferences preferences = OmahaBase.getSharedPreferences();
+ status.latestVersion = preferences.getString(OmahaBase.PREF_LATEST_MODIFIED_VERSION, "");
+
+ status.updateState = UpdateState.NONE;
+ if (status.latestVersion != null && status.latestVersion.length() != 0) {
+ VersionNumber latestVersion = VersionNumber.fromString(status.latestVersion);
+ if (latestVersion == null) {
+ Log.e(TAG, "BromiteUpdater: stored latest version '%s' is invalid", status.latestVersion);
+ } else if (OmahaBase.isNewVersionAvailableByVersion(latestVersion)) {
+ status.updateState = UpdateState.INLINE_UPDATE_AVAILABLE;
+ status.updateUrl = BromiteInlineUpdateController.getDownloadUrl();
+ return status;
+ }
+ String latestUpstreamVersion = preferences.getString(OmahaBase.PREF_LATEST_UPSTREAM_VERSION, "");
+ if (latestUpstreamVersion != null && latestUpstreamVersion.length() != 0) {
+ VersionNumber upstreamVersion = VersionNumber.fromString(latestUpstreamVersion);
+ if (upstreamVersion == null) {
+ Log.e(TAG, "BromiteUpdater: stored latest upstream version '%s' is invalid", latestUpstreamVersion);
+ } else if (OmahaBase.isNewVersionAvailableByVersion(upstreamVersion)) {
+ status.updateUrl = BromiteInlineUpdateController.VULNERABLE_VERSION_DOC_URL;
+ status.updateState = UpdateState.VULNERABLE_VERSION;
+ return status;
+ }
+ }
}
return status;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
@@ -51,7 +51,8 @@ public class VersionNumberGetter {
private static VersionNumberGetter sInstanceForTests;
/** If false, OmahaClient will never report that a newer version is available. */
- private static boolean sEnableUpdateDetection = true;
+ // it must be false to disable version control via Omaha server
+ private static boolean sEnableUpdateDetection = false;
protected VersionNumberGetter() {}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/BromiteInlineUpdateController.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/BromiteInlineUpdateController.java
new file mode 100644
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/BromiteInlineUpdateController.java
@@ -0,0 +1,272 @@
+// Copyright 2021 The Ungoogled Chromium Authors. All rights reserved.
+//
+// This file is part of Ungoogled Chromium Android.
+//
+// Ungoogled Chromium Android is free software: you can redistribute it
+// and/or modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or any later version.
+//
+// Ungoogled Chromium Android is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Ungoogled Chromium Android. If not,
+// see <https://www.gnu.org/licenses/>.
+
+package org.chromium.chrome.browser.omaha.inline;
+
+import static org.chromium.chrome.browser.omaha.UpdateConfigs.getUpdateNotificationInterval;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.text.format.DateUtils;
+import org.chromium.build.BuildConfig;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Callback;
+import org.chromium.base.Log;
+import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
+import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.omaha.OmahaBase;
+import org.chromium.chrome.browser.omaha.UpdateConfigs;
+import org.chromium.chrome.browser.omaha.UpdateStatusProvider;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabCreator;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.base.PageTransition;
+import org.chromium.net.NetworkTrafficAnnotationTag;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.util.regex.Pattern;
+
+import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcher;
+import org.chromium.chrome.browser.endpoint_fetcher.EndpointResponse;
+import org.chromium.chrome.browser.omaha.VersionNumber;
+
+public class BromiteInlineUpdateController implements InlineUpdateController {
+
+ private static final String TAG = "BromiteInlineUpdateController";
+ private final String REDIRECT_URL_PREFIX = "https://github.com/bromite/bromite/releases/download/";
+ private static final String UPDATE_VERSION_URL = "https://github.com/bromite/bromite/releases/latest/download/";
+ private final String UPSTREAM_VERSION_URL = "https://www.bromite.org/upstream.txt";
+ public static final String VULNERABLE_VERSION_DOC_URL = "https://www.bromite.org/vulnerable-version";
+
+ public static String getDownloadUrl() {
+ return UPDATE_VERSION_URL + BuildConfig.BUILD_TARGET_CPU + "_ChromePublic.apk";
+ }
+
+ private static final NetworkTrafficAnnotationTag TRAFFIC_ANNOTATION =
+ NetworkTrafficAnnotationTag.createComplete("bromite_inline_update_controller",
+ "semantics {"
+ + " sender: 'Bromite Inline Update (Android)'"
+ + " description:"
+ + " 'Check for update'"
+ + " trigger: 'This request is made once, on first run'"
+ + " data: 'None.'"
+ + " destination: OTHER"
+ + " internal {"
+ + " contacts {"
+ + " email: 'uazo@users.noreply.github.com'"
+ + " }"
+ + " contacts {"
+ + " email: 'uazo@users.noreply.github.com'"
+ + " }"
+ + " }"
+ + " user_data {"
+ + " type: NONE"
+ + " }"
+ + " last_reviewed: '2023-01-01'"
+ + "}"
+ + "policy {"
+ + " cookies_allowed: NO"
+ + " setting: 'Can be disabled in Settings.'"
+ + " policy_exception_justification: 'Not implemented.'"
+ + "}");
+
+ private boolean mEnabled = true;
+ private Runnable mCallback;
+ private @Nullable @UpdateStatusProvider.UpdateState Integer mUpdateState =
+ UpdateStatusProvider.UpdateState.NONE;
+ private String mUpdateUrl = "";
+
+ BromiteInlineUpdateController(Runnable callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (mEnabled == enabled) return;
+
+ mEnabled = enabled;
+ // check for an update when state changes
+ if (mEnabled) pullCurrentState();
+ }
+
+ @Override
+ public @Nullable @UpdateStatusProvider.UpdateState Integer getStatus() {
+ if (mEnabled) pullCurrentState();
+ return mUpdateState;
+ }
+
+ @Override
+ public String getUpdateUrl() {
+ // relies on a prior call to getStatus() to have state and URL correctly pulled
+ return mUpdateUrl;
+ }
+
+ @Override
+ public void startUpdate(Activity activity) {
+ assert ChromeActivity.class.isInstance(activity);
+ ChromeActivity thisActivity = (ChromeActivity) activity;
+ // Always open in new incognito tab
+ TabCreator tabCreator = thisActivity.getTabCreator(true);
+ tabCreator.createNewTab(new LoadUrlParams(mUpdateUrl, PageTransition.AUTO_BOOKMARK),
+ TabLaunchType.FROM_LINK, thisActivity.getActivityTab());
+ }
+
+ @Override
+ public void completeUpdate() {
+ }
+
+ private void pullCurrentState() {
+ if (OmahaBase.getSharedPreferences()
+ .getBoolean(OmahaBase.PREF_ALLOW_INLINE_UPDATE, false) == false) {
+ Log.i(TAG, "BromiteUpdater: disabled by user");
+ return;
+ }
+
+ // do not pull state if there is already a state set
+ if (mUpdateState != UpdateStatusProvider.UpdateState.NONE)
+ return;
+
+ if (shallUpdate() == false)
+ return;
+
+ switch (mUpdateState) {
+ case UpdateStatusProvider.UpdateState.INLINE_UPDATE_AVAILABLE:
+ break;
+ case UpdateStatusProvider.UpdateState.NONE:
+ OmahaBase.resetUpdatePrefs();
+ checkLatestVersion((latestVersion) -> {
+ if (latestVersion == null) return;
+
+ if (OmahaBase.isNewVersionAvailableByVersion(latestVersion)) {
+ postStatus(UpdateStatusProvider.UpdateState.INLINE_UPDATE_AVAILABLE, getDownloadUrl());
+ } else {
+ checkLatestUpstreamVersion((latestUpstreamVersion) -> {
+ if (latestUpstreamVersion == null) return;
+ if (OmahaBase.isNewVersionAvailableByVersion(latestUpstreamVersion)) {
+ postStatus(UpdateStatusProvider.UpdateState.VULNERABLE_VERSION, VULNERABLE_VERSION_DOC_URL);
+ }
+ });
+ }
+ });
+ break;
+ case UpdateStatusProvider.UpdateState.INLINE_UPDATE_READY:
+ // Intentional fall through.
+ case UpdateStatusProvider.UpdateState.INLINE_UPDATE_FAILED:
+ // Intentional fall through.
+ case UpdateStatusProvider.UpdateState.INLINE_UPDATE_DOWNLOADING:
+ // Intentional fall through.
+ case UpdateStatusProvider.UpdateState.UNSUPPORTED_OS_VERSION:
+ // Intentional fall through.
+ case UpdateStatusProvider.UpdateState.VULNERABLE_VERSION:
+ // Intentional fall through.
+ default:
+ return;
+ }
+ }
+
+ private boolean shallUpdate() {
+ long currentTime = System.currentTimeMillis();
+ SharedPreferences preferences = OmahaBase.getSharedPreferences();
+ long lastPushedTimeStamp = preferences.getLong(OmahaBase.PREF_TIMESTAMP_OF_REQUEST, 0);
+ return currentTime - lastPushedTimeStamp >= getUpdateNotificationInterval();
+ }
+
+ private void checkLatestVersion(final Callback<VersionNumber> callback) {
+ assert UPDATE_VERSION_URL != null;
+
+ String urlToCheck = getDownloadUrl();
+ Log.i(TAG, "BromiteUpdater: fetching with HEAD '%s'", urlToCheck);
+
+ EndpointFetcher.nativeHeadWithNoAuth(
+ (endpointResponse) -> {
+ boolean versionFound = false;
+ String redirectURL = endpointResponse.getRedirectUrl();
+ if (redirectURL != null) {
+ Log.i(TAG, "BromiteUpdater: obtained response '%s' and redirect URL '%s'", endpointResponse.getResponseString(), redirectURL);
+ if (redirectURL.indexOf(REDIRECT_URL_PREFIX) == 0) {
+ redirectURL = redirectURL.substring(REDIRECT_URL_PREFIX.length());
+ String[] parts = redirectURL.split(Pattern.quote("/"));
+ if (parts.length > 0) {
+ VersionNumber version = VersionNumber.fromString(parts[0]);
+ if (version != null) {
+ versionFound = true;
+ OmahaBase.setLatestModifiedVersion(parts[0]);
+ callback.onResult(version);
+ return;
+ }
+ }
+ }
+ }
+ if (!versionFound) {
+ // retry after 1 hour
+ OmahaBase.updateLastPushedTimeStamp(
+ System.currentTimeMillis() - getUpdateNotificationInterval() -
+ DateUtils.HOUR_IN_MILLIS);
+ Log.e(TAG, "BromiteUpdater: failed, will retry in 1 hour");
+ }
+
+ callback.onResult(null);
+ },
+ Profile.getLastUsedRegularProfile(),
+ urlToCheck, /*timeout*/5000, /*follow_redirect*/true, TRAFFIC_ANNOTATION);
+ }
+
+ private void checkLatestUpstreamVersion(final Callback<VersionNumber> callback) {
+ Log.i(TAG, "BromiteUpdater: fetching with GET '%s'", UPSTREAM_VERSION_URL);
+
+ EndpointFetcher.nativeFetchWithNoAuth(
+ (endpointResponse) -> {
+ String response = endpointResponse.getResponseString().trim();
+ Log.i(TAG, "BromiteUpdater: obtained upstream version update response '%s'", response);
+ VersionNumber version = VersionNumber.fromString(response);
+ if (version != null) {
+ OmahaBase.updateLastPushedTimeStamp(System.currentTimeMillis());
+ OmahaBase.setLatestUpstreamVersion(response);
+ callback.onResult(version);
+ return;
+ }
+ // retry after 1 hour
+ OmahaBase.updateLastPushedTimeStamp(
+ System.currentTimeMillis() - getUpdateNotificationInterval() -
+ DateUtils.HOUR_IN_MILLIS);
+ Log.e(TAG, "BromiteUpdater: failed to fetch upstream version, will retry in 1 hour");
+
+ callback.onResult(null);
+ },
+ Profile.getLastUsedRegularProfile(),
+ UPSTREAM_VERSION_URL, /*timeout*/5000, /*follow_redirect*/false, TRAFFIC_ANNOTATION);
+ }
+
+ private void postStatus(@UpdateStatusProvider.UpdateState int status, String updateUrl) {
+ mUpdateState = status;
+ mUpdateUrl = updateUrl;
+ PostTask.postTask(TaskTraits.UI_DEFAULT, mCallback);
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateController.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateController.java
new file mode 100644
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateController.java
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omaha.inline;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.chrome.browser.omaha.UpdateStatusProvider;
+
+/**
+ * Helper for gluing interactions with the Play store's AppUpdateManager with Chrome. This
+ * involves hooking up to Play as a listener for install state changes, should only happen if we are
+ * in the foreground.
+ */
+public interface InlineUpdateController {
+ /**
+ * Enables or disables the controller. It will trigger an update check when previously disabled.
+ * @param enabled true iff the controller should be enabled.
+ */
+ void setEnabled(boolean enabled);
+
+ /**
+ * @return The current state of the inline update process. May be {@code null} if the state
+ * hasn't been determined yet.
+ */
+ @Nullable
+ @UpdateStatusProvider.UpdateState
+ Integer getStatus();
+
+ /**
+ * @return The current update URL for the inline update process. May be an empty string if the state
+ * hasn't been determined yet or if state does not specify one.
+ */
+ String getUpdateUrl();
+
+ /**
+ * Starts the update, if possible. This will send an {@link Intent} out to play, which may
+ * cause Chrome to move to the background.
+ * @param activity The {@link Activity} to use to interact with Play.
+ */
+ void startUpdate(Activity activity);
+
+ /**
+ * Completes the Play installation process, if possible. This may cause Chrome to restart.
+ */
+ void completeUpdate();
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateControllerFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateControllerFactory.java
new file mode 100644
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/InlineUpdateControllerFactory.java
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omaha.inline;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.omaha.UpdateConfigs;
+
+/**
+ * A factory that creates an {@link InlineUpdateController} instance.
+ */
+public class InlineUpdateControllerFactory {
+ /**
+ * @return A new {@link InlineUpdateController}.
+ */
+ public static InlineUpdateController create(Runnable callback) {
+ return new BromiteInlineUpdateController(callback);
+ }
+}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3112,6 +3112,7 @@ static_library("browser") {
"download/android/service/download_task_scheduler.h",
"download/android/string_utils.cc",
"download/download_crx_util_android.cc",
+ "endpoint_fetcher/endpoint_fetcher_android.cc",
"enterprise/reporting/browser_report_generator_android.cc",
"enterprise/reporting/browser_report_generator_android.h",
"enterprise/reporting/profile_report_generator_android.cc",
@@ -3451,6 +3452,7 @@ static_library("browser") {
"//chrome/browser/content_extraction/android:jni_headers",
"//chrome/browser/device_reauth/android:jni_headers",
"//chrome/browser/download/internal/android",
+ "//chrome/browser/endpoint_fetcher:jni_headers",
"//chrome/browser/feed/android:jni_headers",
"//chrome/browser/feedback/android",
"//chrome/browser/feedback/android:jni_headers",
diff --git a/chrome/browser/endpoint_fetcher/BUILD.gn b/chrome/browser/endpoint_fetcher/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/chrome/browser/endpoint_fetcher/BUILD.gn
@@ -0,0 +1,32 @@
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+import("//third_party/jni_zero/jni_zero.gni")
+
+android_library("java") {
+ deps = [
+ ":jni_headers",
+ "//base:base_java",
+ "//build/android:build_java",
+ "//chrome/browser/profiles/android:java",
+ "//net/android:net_java",
+ "//third_party/androidx:androidx_annotation_annotation_java",
+ "//third_party/jni_zero:jni_zero_java",
+ ]
+ srcjar_deps = [ ":jni_headers" ]
+ sources = [
+ "java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointFetcher.java",
+ "java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointResponse.java",
+ "java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointHeaderResponse.java",
+ ]
+}
+
+generate_jni("jni_headers") {
+ sources = [
+ "java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointFetcher.java",
+ "java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointResponse.java",
+ "java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointHeaderResponse.java",
+ ]
+}
diff --git a/chrome/browser/endpoint_fetcher/DEPS b/chrome/browser/endpoint_fetcher/DEPS
new file mode 100644
--- /dev/null
+++ b/chrome/browser/endpoint_fetcher/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/endpoint_fetcher",
+]
diff --git a/chrome/browser/endpoint_fetcher/OWNERS b/chrome/browser/endpoint_fetcher/OWNERS
new file mode 100644
--- /dev/null
+++ b/chrome/browser/endpoint_fetcher/OWNERS
@@ -0,0 +1 @@
+file://chrome/browser/complex_tasks/OWNERS
diff --git a/chrome/browser/endpoint_fetcher/endpoint_fetcher_android.cc b/chrome/browser/endpoint_fetcher/endpoint_fetcher_android.cc
new file mode 100644
--- /dev/null
+++ b/chrome/browser/endpoint_fetcher/endpoint_fetcher_android.cc
@@ -0,0 +1,184 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/endpoint_fetcher/endpoint_fetcher.h"
+
+#include "base/android/callback_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "chrome/browser/endpoint_fetcher/jni_headers/EndpointFetcher_jni.h"
+#include "chrome/browser/endpoint_fetcher/jni_headers/EndpointResponse_jni.h"
+#include "chrome/browser/endpoint_fetcher/jni_headers/EndpointHeaderResponse_jni.h"
+#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/common/channel_info.h"
+#include "components/signin/public/base/consent_level.h"
+#include "components/version_info/channel.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace {
+static void OnEndpointFetcherComplete(
+ const base::android::JavaRef<jobject>& jcaller,
+ // Passing the endpoint_fetcher ensures the endpoint_fetcher's
+ // lifetime extends to the callback and is not destroyed
+ // prematurely (which would result in cancellation of the request).
+ std::unique_ptr<EndpointFetcher> endpoint_fetcher,
+ std::unique_ptr<EndpointResponse> endpoint_response) {
+ base::android::RunObjectCallbackAndroid(
+ jcaller, Java_EndpointResponse_createEndpointResponse(
+ base::android::AttachCurrentThread(),
+ base::android::ConvertUTF8ToJavaString(
+ base::android::AttachCurrentThread(),
+ std::move(endpoint_response->response))));
+}
+
+static void OnEndpointFetcherHeadComplete(
+ const base::android::JavaRef<jobject>& jcaller,
+ // Passing the endpoint_fetcher ensures the endpoint_fetcher's
+ // lifetime extends to the callback and is not destroyed
+ // prematurely (which would result in cancellation of the request).
+ std::unique_ptr<EndpointFetcher> endpoint_fetcher,
+ std::unique_ptr<EndpointResponse> endpoint_response) {
+ base::android::RunObjectCallbackAndroid(
+ jcaller, Java_EndpointHeaderResponse_createEndpointResponse(
+ base::android::AttachCurrentThread(),
+ base::android::ConvertUTF8ToJavaString(
+ base::android::AttachCurrentThread(),
+ std::move(endpoint_response->response)),
+ base::android::ConvertUTF8ToJavaString(
+ base::android::AttachCurrentThread(),
+ std::move(endpoint_response->redirect_url))));
+}
+} // namespace
+
+// TODO(crbug.com/1077537) Create a KeyProvider so
+// we can have one centralized API.
+
+static void JNI_EndpointFetcher_NativeFetchOAuth(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jprofile,
+ const base::android::JavaParamRef<jstring>& joauth_consumer_name,
+ const base::android::JavaParamRef<jstring>& jurl,
+ const base::android::JavaParamRef<jstring>& jhttps_method,
+ const base::android::JavaParamRef<jstring>& jcontent_type,
+ const base::android::JavaParamRef<jobjectArray>& jscopes,
+ const base::android::JavaParamRef<jstring>& jpost_data,
+ jlong jtimeout,
+ jint jannotation_hash_code,
+ const base::android::JavaParamRef<jobject>& jcallback) {
+ std::vector<std::string> scopes;
+ base::android::AppendJavaStringArrayToStringVector(env, jscopes, &scopes);
+ auto endpoint_fetcher = std::make_unique<EndpointFetcher>(
+ ProfileAndroid::FromProfileAndroid(jprofile)
+ ->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess(),
+ base::android::ConvertJavaStringToUTF8(env, joauth_consumer_name),
+ GURL(base::android::ConvertJavaStringToUTF8(env, jurl)),
+ base::android::ConvertJavaStringToUTF8(env, jhttps_method),
+ base::android::ConvertJavaStringToUTF8(env, jcontent_type), scopes,
+ base::Milliseconds(jtimeout), base::android::ConvertJavaStringToUTF8(env, jpost_data),
+ net::NetworkTrafficAnnotationTag::FromJavaAnnotation(
+ jannotation_hash_code),
+ IdentityManagerFactory::GetForProfile(
+ ProfileAndroid::FromProfileAndroid(jprofile)),
+ // TODO(crbug.com/1466445): ConsentLevel::kSync is deprecated and should
+ // be removed. See ConsentLevel::kSync documentation for details.
+ signin::ConsentLevel::kSync);
+ auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+ endpoint_fetcher_ptr->Fetch(
+ base::BindOnce(&OnEndpointFetcherComplete,
+ base::android::ScopedJavaGlobalRef<jobject>(jcallback),
+ // unique_ptr endpoint_fetcher is passed until the callback
+ // to ensure its lifetime across the request.
+ std::move(endpoint_fetcher)));
+}
+
+static void JNI_EndpointFetcher_NativeFetchChromeAPIKey(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jprofile,
+ const base::android::JavaParamRef<jstring>& jurl,
+ const base::android::JavaParamRef<jstring>& jhttps_method,
+ const base::android::JavaParamRef<jstring>& jcontent_type,
+ const base::android::JavaParamRef<jstring>& jpost_data,
+ jlong jtimeout,
+ const base::android::JavaParamRef<jobjectArray>& jheaders,
+ jint jannotation_hash_code,
+ const base::android::JavaParamRef<jobject>& jcallback) {
+ std::vector<std::string> headers;
+ base::android::AppendJavaStringArrayToStringVector(env, jheaders, &headers);
+ auto endpoint_fetcher = std::make_unique<EndpointFetcher>(
+ ProfileAndroid::FromProfileAndroid(jprofile)
+ ->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess(),
+ GURL(base::android::ConvertJavaStringToUTF8(env, jurl)),
+ base::android::ConvertJavaStringToUTF8(env, jhttps_method),
+ base::android::ConvertJavaStringToUTF8(env, jcontent_type),
+ base::Milliseconds(jtimeout),
+ base::android::ConvertJavaStringToUTF8(env, jpost_data), headers,
+ net::NetworkTrafficAnnotationTag::FromJavaAnnotation(
+ jannotation_hash_code),
+ chrome::GetChannel() == version_info::Channel::STABLE);
+ auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+ endpoint_fetcher_ptr->PerformRequest(
+ base::BindOnce(&OnEndpointFetcherComplete,
+ base::android::ScopedJavaGlobalRef<jobject>(jcallback),
+ // unique_ptr endpoint_fetcher is passed until the callback
+ // to ensure its lifetime across the request.
+ std::move(endpoint_fetcher)),
+ nullptr);
+}
+
+static void JNI_EndpointFetcher_NativeFetchWithNoAuth(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jprofile,
+ const base::android::JavaParamRef<jstring>& jurl,
+ jlong jtimeout, jboolean intercept_redirect,
+ jint jannotation_hash_code,
+ const base::android::JavaParamRef<jobject>& jcallback) {
+ auto endpoint_fetcher = std::make_unique<EndpointFetcher>(
+ ProfileAndroid::FromProfileAndroid(jprofile)
+ ->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess(),
+ GURL(base::android::ConvertJavaStringToUTF8(env, jurl)),
+ "GET",
+ jtimeout,
+ intercept_redirect,
+ net::NetworkTrafficAnnotationTag::FromJavaAnnotation(
+ jannotation_hash_code));
+ auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+ endpoint_fetcher_ptr->PerformRequest(
+ base::BindOnce(&OnEndpointFetcherComplete,
+ base::android::ScopedJavaGlobalRef<jobject>(jcallback),
+ // unique_ptr endpoint_fetcher is passed until the callback
+ // to ensure its lifetime across the request.
+ std::move(endpoint_fetcher)),
+ nullptr);
+}
+
+static void JNI_EndpointFetcher_NativeHeadWithNoAuth(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jprofile,
+ const base::android::JavaParamRef<jstring>& jurl,
+ jlong jtimeout, jboolean intercept_redirect,
+ jint jannotation_hash_code,
+ const base::android::JavaParamRef<jobject>& jcallback) {
+ auto endpoint_fetcher = std::make_unique<EndpointFetcher>(
+ ProfileAndroid::FromProfileAndroid(jprofile)
+ ->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess(),
+ GURL(base::android::ConvertJavaStringToUTF8(env, jurl)),
+ "HEAD",
+ jtimeout,
+ intercept_redirect,
+ net::NetworkTrafficAnnotationTag::FromJavaAnnotation(
+ jannotation_hash_code));
+ auto* const endpoint_fetcher_ptr = endpoint_fetcher.get();
+ endpoint_fetcher_ptr->PerformRequest(
+ base::BindOnce(&OnEndpointFetcherHeadComplete,
+ base::android::ScopedJavaGlobalRef<jobject>(jcallback),
+ // unique_ptr endpoint_fetcher is passed until the callback
+ // to ensure its lifetime across the request.
+ std::move(endpoint_fetcher)),
+ nullptr);
+}
diff --git a/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointFetcher.java b/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointFetcher.java
new file mode 100644
--- /dev/null
+++ b/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointFetcher.java
@@ -0,0 +1,165 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.endpoint_fetcher;
+
+import androidx.annotation.MainThread;
+
+import org.jni_zero.NativeMethods;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.net.NetworkTrafficAnnotationTag;
+
+/**
+ * EndpointFetcher uses native EndpointFetcher to call a HTTPS endpoint and return
+ * the response. The call to native EndpointFetcher is achieved over a static call
+ * over JNI. The native EndpointFetcher is created during the static call and
+ * destroyed in the callback function.
+ * EndpointFetcher currently doesn't support incognito mode.
+ * If the request times out an empty response will be returned. There will also
+ * be an error code indicating timeout once more detailed error messaging is added
+ * TODO(crbug.com/993393).
+ */
+public final class EndpointFetcher {
+ private EndpointFetcher() {}
+
+ /**
+ * Calls an endpoint using OAuth and returns the response via a callback
+ * @param callback callback function response is returned in
+ * @param profile profile via which the endpoint is called
+ * @param oathConsumerName consumer name for OAuth
+ * @param url endpoint URL called
+ * @param httpsMethod the HTTPS method used e.g. "GET" or "POST"
+ * @param contentType the content type e.g. "application/json"
+ * @param scopes scopes used as part of authentication
+ * @param postData data for a "POST" request
+ * @param timeout time after which the request will terminate in the event a response hasn't
+ * been received
+ */
+ @MainThread
+ public static void fetchUsingOAuth(
+ Callback<EndpointResponse> callback,
+ Profile profile,
+ String oathConsumerName,
+ String url,
+ String httpsMethod,
+ String contentType,
+ String[] scopes,
+ String postData,
+ long timeout,
+ NetworkTrafficAnnotationTag annotation) {
+ // EndpointFetcher currently does not support incognito mode
+ assert !profile.isOffTheRecord();
+ EndpointFetcherJni.get()
+ .nativeFetchOAuth(
+ profile,
+ oathConsumerName,
+ url,
+ httpsMethod,
+ contentType,
+ scopes,
+ postData,
+ timeout,
+ annotation.getHashCode(),
+ callback);
+ }
+
+ /**
+ * Calls an endpoint using Chrome API Key returns the response via a callback
+ * @param callback to pass the response back in
+ * @param profile via which the endpoint is called
+ * @param url endpoint URL called
+ * @param httpsMethod the HTTPS method used e.g. "GET" or "POST"
+ * @param contentType the content type e.g. "application/json"
+ * @param postData data for a "POST" request
+ * @param timeout time after which the request will terminate in the event a response hasn't
+ * been received
+ * @param headers headers for the request. Key/value pairs are stored sequentially in the array.
+ */
+ @MainThread
+ public static void fetchUsingChromeAPIKey(
+ Callback<EndpointResponse> callback,
+ Profile profile,
+ String url,
+ String httpsMethod,
+ String contentType,
+ String postData,
+ long timeout,
+ String[] headers,
+ NetworkTrafficAnnotationTag annotation) {
+ // EndpointFetcher currently does not support incognito mode
+ // assert !profile.isOffTheRecord();
+ EndpointFetcherJni.get()
+ .nativeFetchChromeAPIKey(
+ profile,
+ url,
+ httpsMethod,
+ contentType,
+ postData,
+ timeout,
+ headers,
+ annotation.getHashCode(),
+ callback);
+ }
+
+ @MainThread
+ public static void nativeHeadWithNoAuth(
+ Callback<EndpointHeaderResponse> callback, Profile profile,
+ String url, long timeout, boolean allow_redirect,
+ NetworkTrafficAnnotationTag annotation) {
+ EndpointFetcherJni.get().nativeHeadWithNoAuth(
+ profile, url, timeout, allow_redirect, annotation.getHashCode(), callback);
+ }
+
+ @MainThread
+ public static void nativeFetchWithNoAuth(
+ Callback<EndpointResponse> callback, Profile profile,
+ String url, long timeout, boolean allow_redirect,
+ NetworkTrafficAnnotationTag annotation) {
+ EndpointFetcherJni.get().nativeFetchWithNoAuth(
+ profile, url, timeout, allow_redirect, annotation.getHashCode(), callback);
+ }
+
+ @NativeMethods
+ public interface Natives {
+ void nativeFetchOAuth(
+ Profile profile,
+ String oathConsumerName,
+ String url,
+ String httpsMethod,
+ String contentType,
+ String[] scopes,
+ String postData,
+ long timeout,
+ int annotationHashCode,
+ Callback<EndpointResponse> callback);
+
+ void nativeFetchChromeAPIKey(
+ Profile profile,
+ String url,
+ String httpsMethod,
+ String contentType,
+ String postData,
+ long timeout,
+ String[] headers,
+ int annotationHashCode,
+ Callback<EndpointResponse> callback);
+
+ void nativeFetchWithNoAuth(
+ Profile profile,
+ String url,
+ long timeout,
+ boolean allow_redirect,
+ int annotationHashCode,
+ Callback<EndpointResponse> callback);
+ void nativeHeadWithNoAuth(
+ Profile profile,
+ String url,
+ long timeout,
+ boolean allow_redirect,
+ int annotationHashCode,
+ Callback<EndpointHeaderResponse> callback);
+ }
+}
diff --git a/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointHeaderResponse.java b/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointHeaderResponse.java
new file mode 100644
--- /dev/null
+++ b/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointHeaderResponse.java
@@ -0,0 +1,31 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.endpoint_fetcher;
+
+import org.jni_zero.CalledByNative;
+
+public class EndpointHeaderResponse {
+ private final String mResponseString;
+ private final String mRedirectUrl;
+
+ public EndpointHeaderResponse(String responseString, String redirectUrl) {
+ mResponseString = responseString;
+ mRedirectUrl = redirectUrl;
+ }
+
+ public String getResponseString() {
+ return mResponseString;
+ }
+
+ public String getRedirectUrl() {
+ return mRedirectUrl;
+ }
+
+ @CalledByNative
+ private static EndpointHeaderResponse createEndpointResponse(
+ String response, String redirectUrl) {
+ return new EndpointHeaderResponse(response, redirectUrl);
+ }
+}
diff --git a/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointResponse.java b/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointResponse.java
new file mode 100644
--- /dev/null
+++ b/chrome/browser/endpoint_fetcher/java/src/org/chromium/chrome/browser/endpoint_fetcher/EndpointResponse.java
@@ -0,0 +1,30 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.endpoint_fetcher;
+
+import org.jni_zero.CalledByNative;
+
+/** Encapsulates the response from the {@Link EndpointFetcher} */
+public class EndpointResponse {
+ private final String mResponseString;
+
+ /**
+ * Create the EndpointResponse
+ * @param responseString the response string acquired from the endpoint
+ */
+ public EndpointResponse(String responseString) {
+ mResponseString = responseString;
+ }
+
+ /** Response string acquired from calling an endpoint */
+ public String getResponseString() {
+ return mResponseString;
+ }
+
+ @CalledByNative
+ private static EndpointResponse createEndpointResponse(String response) {
+ return new EndpointResponse(response);
+ }
+}
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -235,6 +235,7 @@ const base::Feature* const kFeaturesExposedToJava[] = {
&kPreconnectOnTabCreation,
&kPriceChangeModule,
&kPwaRestoreUi,
+ &kInlineUpdateFlow,
&kPwaUniversalInstallUi,
&kOmahaMinSdkVersionAndroid,
&kShortCircuitUnfocusAnimation,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -315,6 +315,7 @@ public abstract class ChromeFeatureList {
"IncognitoReauthenticationForAndroid";
public static final String INCOGNITO_SCREENSHOT = "IncognitoScreenshot";
public static final String INSTANT_START = "InstantStart";
+ public static final String INLINE_UPDATE_FLOW = "InlineUpdateFlow";
public static final String INTEREST_FEED_V2_HEARTS = "InterestFeedV2Hearts";
public static final String KID_FRIENDLY_CONTENT_FEED = "KidFriendlyContentFeed";
public static final String LENS_CAMERA_ASSISTED_SEARCH = "LensCameraAssistedSearch";
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2050,6 +2050,12 @@ Your Google account may have other forms of browsing history like searches and a
<message name="IDS_DEPRECATION_WARNING" desc="Warning about Chrome updates no longer being supported">
Chrome updates are no longer supported for this version of Android
</message>
+ <message name="IDS_ALLOW_INLINE_UPDATE_TITLE" desc="Title for allow inline update preference">
+ Allow checking for updates
+ </message>
+ <message name="IDS_ALLOW_INLINE_UPDATE_SUMMARY" desc="Summary for allow inline update preference">
+ Notify about new releases by periodically checking for their availability
+ </message>
<!-- Account management UI strings. -->
<message name="IDS_ACCOUNT_MANAGEMENT_TITLE" desc="Header title for the account management screen. [CHAR_LIMIT=32]">
@@ -3732,7 +3738,10 @@ To change this setting, <ph name="BEGIN_LINK">BEGIN_LINK</ph>clear the Chrome da
<!-- Main menu items -->
<message name="IDS_MENU_UPDATE" desc="Menu item for updating chrome. [CHAR_LIMIT=24]">
- Update Chrome
+ Update Bromite
+ </message>
+ <message name="IDS_MENU_UPDATE_SUMMARY_VULNERABLE" desc="Content description for the menu button when it is covered by the update icon that is displayed when the current version of Bromite is vulnerable.">
+ Update not available. Read more
</message>
<message name="IDS_MENU_UPDATE_SUMMARY_DEFAULT" desc="Summary string for update menu item explaining that a newer version of Chrome is available. [CHAR_LIMIT=30]">
Newer version is available
@@ -3743,6 +3752,18 @@ To change this setting, <ph name="BEGIN_LINK">BEGIN_LINK</ph>clear the Chrome da
<message name="IDS_MENU_UPDATE_UNSUPPORTED_SUMMARY_DEFAULT" desc="Summary string for update menu item explaining that the Android version on the device is unsupported. [CHAR_LIMIT=30]">
Android version is unsupported
</message>
+ <message name="IDS_MENU_INLINE_UPDATE_DOWNLOADING" desc="Menu item for when Chrome is in the process of updating. [CHAR_LIMIT=24]">
+ Downloading…
+ </message>
+ <message name="IDS_MENU_INLINE_UPDATE_FAILED" desc="Menu item for when Chrome failed to download an update. [CHAR_LIMIT=24]">
+ Couldnt download
+ </message>
+ <message name="IDS_MENU_INLINE_UPDATE_READY" desc="Menu item text that is shown when Chrome has downloaded an update for itself and is ready to be restarted, which will apply the update. [CHAR_LIMIT=24]">
+ Update ready
+ </message>
+ <message name="IDS_MENU_INLINE_UPDATE_READY_SUMMARY" desc="Summary text that is shown when Chrome has downloaded an update for itself and is ready to be restarted, which will apply the update. This lets the user know the update will restart. [CHAR_LIMIT=24]">
+ Restart Bromite
+ </message>
<message name="IDS_MENU_NEW_WINDOW" desc="Menu item for opening a new window. [CHAR_LIMIT=27]">
New window
</message>
diff --git a/components/endpoint_fetcher/endpoint_fetcher.cc b/components/endpoint_fetcher/endpoint_fetcher.cc
--- a/components/endpoint_fetcher/endpoint_fetcher.cc
+++ b/components/endpoint_fetcher/endpoint_fetcher.cc
@@ -19,6 +19,11 @@
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
+// used for the Bromite customization
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/resource_request.h"
+
namespace {
const char kContentTypeKey[] = "Content-Type";
const char kDeveloperKey[] = "X-Developer-Key";
@@ -65,6 +70,7 @@ EndpointFetcher::EndpointFetcher(
http_method_(http_method),
content_type_(content_type),
timeout_(timeout),
+ intercept_redirect_(false),
post_data_(post_data),
headers_(headers),
annotation_tag_(annotation_tag),
@@ -83,6 +89,7 @@ EndpointFetcher::EndpointFetcher(
http_method_("GET"),
content_type_(std::string()),
timeout_(base::Milliseconds(0)),
+ intercept_redirect_(false),
post_data_(std::string()),
annotation_tag_(annotation_tag),
url_loader_factory_(url_loader_factory),
@@ -108,6 +115,7 @@ EndpointFetcher::EndpointFetcher(
http_method_(http_method),
content_type_(content_type),
timeout_(timeout),
+ intercept_redirect_(false),
post_data_(post_data),
annotation_tag_(annotation_tag),
url_loader_factory_(url_loader_factory),
@@ -135,6 +143,7 @@ EndpointFetcher::EndpointFetcher(
http_method_(http_method),
content_type_(content_type),
timeout_(timeout),
+ intercept_redirect_(false),
post_data_(post_data),
headers_(headers),
cors_exempt_headers_(cors_exempt_headers),
@@ -147,11 +156,29 @@ EndpointFetcher::EndpointFetcher(
EndpointFetcher::EndpointFetcher(
const net::NetworkTrafficAnnotationTag& annotation_tag)
: timeout_(kDefaultTimeOut),
+ intercept_redirect_(false),
annotation_tag_(annotation_tag),
identity_manager_(nullptr),
consent_level_(std::nullopt),
sanitize_response_(true) {}
+// constructor used by Bromite
+EndpointFetcher::EndpointFetcher(
+ const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
+ const GURL& url,
+ const std::string& http_method,
+ int64_t timeout_ms,
+ const bool intercept_redirect,
+ const net::NetworkTrafficAnnotationTag& annotation_tag)
+ : url_(url),
+ http_method_(http_method),
+ timeout_(base::Milliseconds(timeout_ms)),
+ intercept_redirect_(intercept_redirect),
+ annotation_tag_(annotation_tag),
+ url_loader_factory_(url_loader_factory),
+ identity_manager_(nullptr),
+ sanitize_response_(false) {}
+
EndpointFetcher::~EndpointFetcher() = default;
void EndpointFetcher::Fetch(EndpointFetcherCallback endpoint_fetcher_callback) {
@@ -210,6 +237,8 @@ void EndpointFetcher::PerformRequest(
resource_request->method = http_method_;
resource_request->url = url_;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+ resource_request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE
+ | net::LOAD_DO_NOT_SAVE_COOKIES;
if (base::EqualsCaseInsensitiveASCII(http_method_, "POST")) {
resource_request->headers.SetHeader(kContentTypeKey, content_type_);
}
@@ -240,25 +269,50 @@ void EndpointFetcher::PerformRequest(
default:
break;
}
+
+ if (intercept_redirect_ == true) {
+ // will need manual mode to capture the landing page URL
+ resource_request->redirect_mode = network::mojom::RedirectMode::kManual; // default is kFollow
+ }
+
// TODO(crbug.com/997018) Make simple_url_loader_ local variable passed to
// callback
simple_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), annotation_tag_);
+ simple_url_loader_->SetTimeoutDuration(timeout_);
+ simple_url_loader_->SetAllowHttpErrorResults(true);
+
+ if (!response_)
+ response_ = std::make_unique<EndpointResponse>();
+
+ if (intercept_redirect_ == true) {
+ // use a callback to capture landing page URL
+ simple_url_loader_->SetOnRedirectCallback(base::BindRepeating(
+ &EndpointFetcher::OnSimpleLoaderRedirect, base::Unretained(this)));
+ }
if (base::EqualsCaseInsensitiveASCII(http_method_, "POST")) {
simple_url_loader_->AttachStringForUpload(post_data_, content_type_);
}
simple_url_loader_->SetRetryOptions(kNumRetries,
network::SimpleURLLoader::RETRY_ON_5XX);
- simple_url_loader_->SetTimeoutDuration(timeout_);
- simple_url_loader_->SetAllowHttpErrorResults(true);
- network::SimpleURLLoader::BodyAsStringCallbackDeprecated
+ LOG(INFO) << "performing " << http_method_ << " request to " << url_;
+ if (base::EqualsCaseInsensitiveASCII(http_method_, "HEAD")) {
+ endpoint_fetcher_callback_ = std::move(endpoint_fetcher_callback);
+
+ simple_url_loader_->DownloadHeadersOnly(
+ url_loader_factory_.get(),
+ base::BindOnce(&EndpointFetcher::OnURLLoadComplete,
+ base::Unretained(this)));
+ } else {
+ network::SimpleURLLoader::BodyAsStringCallbackDeprecated
body_as_string_callback = base::BindOnce(
&EndpointFetcher::OnResponseFetched, weak_ptr_factory_.GetWeakPtr(),
std::move(endpoint_fetcher_callback));
- simple_url_loader_->DownloadToString(
- url_loader_factory_.get(), std::move(body_as_string_callback),
- network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
+ simple_url_loader_->DownloadToString(
+ url_loader_factory_.get(), std::move(body_as_string_callback),
+ network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
+ }
}
void EndpointFetcher::OnResponseFetched(
@@ -339,3 +393,38 @@ void EndpointFetcher::OnSanitizationResult(
std::string EndpointFetcher::GetUrlForTesting() {
return url_.spec();
}
+
+void EndpointFetcher::OnSimpleLoaderRedirect(
+ const GURL& url_before_redirect,
+ const net::RedirectInfo& redirect_info,
+ const network::mojom::URLResponseHead& response_head,
+ std::vector<std::string>* removed_headers) {
+ url_ = redirect_info.new_url;
+ if (response_ && response_->redirect_url.empty()) {
+ response_->redirect_url = url_.spec();
+ response_->response = std::to_string(redirect_info.status_code);
+ } else {
+ LOG(INFO) << "BromiteUpdater: redirect URL is not empty, status code is " << redirect_info.status_code;
+ }
+
+ std::move(endpoint_fetcher_callback_).Run(std::move(response_));
+}
+
+void EndpointFetcher::OnURLLoadComplete(
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ if (!endpoint_fetcher_callback_)
+ return;
+
+ if (headers) {
+ if (response_->redirect_url.empty()) {
+ std::string location;
+ if (simple_url_loader_->ResponseInfo()->headers->IsRedirect(&location)) {
+ response_->redirect_url = location;
+ }
+ }
+ }
+
+ std::string net_error = net::ErrorToString(simple_url_loader_->NetError());
+ response_->response = net_error;
+ std::move(endpoint_fetcher_callback_).Run(std::move(response_));
+}
diff --git a/components/endpoint_fetcher/endpoint_fetcher.h b/components/endpoint_fetcher/endpoint_fetcher.h
--- a/components/endpoint_fetcher/endpoint_fetcher.h
+++ b/components/endpoint_fetcher/endpoint_fetcher.h
@@ -21,6 +21,8 @@
namespace base {
class TimeDelta;
} // namespace base
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
namespace network {
struct ResourceRequest;
@@ -43,6 +45,8 @@ enum class FetchErrorType {
struct EndpointResponse {
std::string response;
+ long last_modified;
+ std::string redirect_url;
int http_status_code{-1};
std::optional<FetchErrorType> error_type;
};
@@ -96,6 +100,14 @@ class EndpointFetcher {
const GURL& url,
const net::NetworkTrafficAnnotationTag& annotation_tag);
+ // Constructor if no authentication is needed, with timeout
+ EndpointFetcher(const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
+ const GURL& url,
+ const std::string& http_method,
+ int64_t timeout_ms,
+ const bool intercept_redirect,
+ const net::NetworkTrafficAnnotationTag& annotation_tag);
+
// Used for tests. Can be used if caller constructs their own
// url_loader_factory and identity_manager.
EndpointFetcher(
@@ -151,6 +163,11 @@ class EndpointFetcher {
void OnSanitizationResult(std::unique_ptr<EndpointResponse> response,
EndpointFetcherCallback endpoint_fetcher_callback,
data_decoder::JsonSanitizer::Result result);
+ void OnURLLoadComplete(scoped_refptr<net::HttpResponseHeaders> headers);
+ void OnSimpleLoaderRedirect(const GURL& url_before_redirect,
+ const net::RedirectInfo& redirect_info,
+ const network::mojom::URLResponseHead& response_head,
+ std::vector<std::string>* removed_headers);
enum AuthType { CHROME_API_KEY, OAUTH, NO_AUTH };
AuthType auth_type_;
@@ -158,10 +175,11 @@ class EndpointFetcher {
// Members set in constructor to be passed to network::ResourceRequest or
// network::SimpleURLLoader.
const std::string oauth_consumer_name_;
- const GURL url_;
+ GURL url_;
const std::string http_method_;
const std::string content_type_;
base::TimeDelta timeout_;
+ const bool intercept_redirect_;
const std::string post_data_;
const std::vector<std::string> headers_;
const std::vector<std::string> cors_exempt_headers_;
@@ -185,6 +203,9 @@ class EndpointFetcher {
access_token_fetcher_;
std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
+ EndpointFetcherCallback endpoint_fetcher_callback_;
+ std::unique_ptr<EndpointResponse> response_;
+
base::WeakPtrFactory<EndpointFetcher> weak_ptr_factory_{this};
};
diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Bromite-auto-updater.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Bromite-auto-updater.inc
new file mode 100644
--- /dev/null
+++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Bromite-auto-updater.inc
@@ -0,0 +1,3 @@
+CROMITE_FEATURE(kInlineUpdateFlow,
+ "InlineUpdateFlow",
+ base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Bromite-auto-updater.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Bromite-auto-updater.inc
new file mode 100644
--- /dev/null
+++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/Bromite-auto-updater.inc
@@ -0,0 +1 @@
+BASE_DECLARE_FEATURE(kInlineUpdateFlow);
--