Commit 3a5cad80 authored by harrisonsean's avatar harrisonsean Committed by Commit Bot

[iOS][Safety Check] Add Omaha support for one off checks

Safety Check on iOS has an update check, to support this we are
adding the ability to do one off checks to Omaha for iOS.

If there is an ongoing scheduled ping, or one is scheduled during the
one off check, it is taken by the one off check.  Otherwise it will
send its own ping.

The one off check updates all user defaults for update status, and
makes Chrome think that it has just shown an update infobar (if an
update is available) so that users triggering an update check aren't
shown an update infobar soon after being told they are out of date by
the update check.

Adds another user default to track if the device is up to date as of
the last check (scheduled or one off). A device that is unable to
update is considered up to date in this user default.

Bug: 1078782
Change-Id: I2e4a1982dd0b34258c78c85ecf23e2f199630f1c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2398725
Commit-Queue: Sean Harrison <harrisonsean@chromium.org>
Reviewed-by: default avatarSylvain Defresne <sdefresne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810532}
parent 27c45df0
......@@ -22,6 +22,7 @@ source_set("omaha") {
"//ios/chrome/browser/browser_state_metrics",
"//ios/chrome/browser/ui/util",
"//ios/chrome/browser/upgrade",
"//ios/chrome/browser/upgrade:public",
"//ios/chrome/common",
"//ios/public/provider/chrome/browser",
"//ios/public/provider/chrome/browser/omaha",
......@@ -44,6 +45,7 @@ source_set("unit_tests") {
"//components/version_info",
"//ios/chrome/browser",
"//ios/chrome/browser/browser_state:test_support",
"//ios/chrome/browser/upgrade:upgrade",
"//ios/chrome/common",
"//ios/chrome/test:test_support",
"//ios/public/provider/chrome/browser",
......
......@@ -35,7 +35,10 @@ class OmahaService {
public:
// Called when an upgrade is recommended.
using UpgradeRecommendedCallback =
base::Callback<void(const UpgradeRecommendedDetails&)>;
base::RepeatingCallback<void(const UpgradeRecommendedDetails&)>;
// Called when a one-off Omaha check returns.
using OneOffCallback = base::OnceCallback<void(UpgradeRecommendedDetails)>;
// Starts the service. Also set the |URLLoaderFactory| necessary to access the
// Omaha server. This method should only be called once. Does nothing if
......@@ -44,6 +47,10 @@ class OmahaService {
pending_url_loader_factory,
const UpgradeRecommendedCallback& callback);
// Performs an immediate check to see if the device is up to date. Start must
// have been previously called.
void CheckNow(OneOffCallback callback);
// Stops the service in preparation for browser shutdown.
static void Stop();
......@@ -61,6 +68,10 @@ class OmahaService {
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, InstallEventMessageTest);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, SendPingFailure);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, SendPingSuccess);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, OneOffSuccess);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, OngoingPingOneOffCallbackUsed);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, OneOffCallbackUsedOnlyOnce);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, ScheduledPingDuringOneOffDropped);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, ParseAndEchoLastServerDate);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, SendInstallEventSuccess);
FRIEND_TEST_ALL_PREFIXES(OmahaServiceTest, SendPingReceiveUpdate);
......@@ -209,9 +220,15 @@ class OmahaService {
// Whether the ping currently being sent is an install (new or update) ping.
bool sending_install_event_;
// If a scheduled ping was canceled.
bool scheduled_ping_canceled_ = false;
// Called to notify that upgrade is recommended.
UpgradeRecommendedCallback upgrade_recommended_callback_;
// Stores the callback for one off Omaha checks.
OneOffCallback one_off_check_callback_;
DISALLOW_COPY_AND_ASSIGN(OmahaService);
};
......
......@@ -33,6 +33,7 @@
#include "ios/chrome/browser/browser_state_metrics/browser_state_metrics.h"
#include "ios/chrome/browser/install_time_util.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/chrome/browser/upgrade/upgrade_constants.h"
#include "ios/chrome/browser/upgrade/upgrade_recommended_details.h"
#include "ios/chrome/common/channel_info.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
......@@ -137,8 +138,8 @@ class XmlWrapper : public OmahaXmlWriter {
// Returns YES if the message has been correctly parsed.
- (BOOL)isCorrect;
// If an upgrade is possible, returns the details of the notification to send.
// Otherwise, return NULL.
// If an upgrade is available, returns the details of the notification to send,
// and returns if Chrome is up to date.
- (UpgradeRecommendedDetails*)upgradeRecommendedDetails;
// If the response was successfully parsed, returns the date according to the
......@@ -240,13 +241,15 @@ class XmlWrapper : public OmahaXmlWriter {
if ([elementName isEqualToString:@"updatecheck"]) {
_updateCheckIsParsed = YES;
NSString* status = [attributeDict valueForKey:@"status"];
_updateInformation = std::make_unique<UpgradeRecommendedDetails>();
if ([status isEqualToString:@"noupdate"]) {
// No update is available on the Market, so we won't get a <url> or
// <manifest> tag.
_urlIsParsed = YES;
_manifestIsParsed = YES;
_updateInformation->is_up_to_date = true;
} else if ([status isEqualToString:@"ok"]) {
_updateInformation = std::make_unique<UpgradeRecommendedDetails>();
_updateInformation->is_up_to_date = false;
} else {
_hasError = YES;
}
......@@ -267,9 +270,8 @@ class XmlWrapper : public OmahaXmlWriter {
NSString* url = [attributeDict valueForKey:@"codebase"];
if ([[url substringFromIndex:([url length] - 1)] isEqualToString:@"/"])
url = [url substringToIndex:([url length] - 1)];
_updateInformation.get()->upgrade_url =
GURL(base::SysNSStringToUTF8(url));
if (!_updateInformation.get()->upgrade_url.is_valid())
_updateInformation->upgrade_url = GURL(base::SysNSStringToUTF8(url));
if (!_updateInformation->upgrade_url.is_valid())
_hasError = YES;
} else {
_hasError = YES;
......@@ -279,7 +281,7 @@ class XmlWrapper : public OmahaXmlWriter {
[attributeDict valueForKey:@"version"]) {
_manifestIsParsed = YES;
DCHECK(_updateInformation);
_updateInformation.get()->next_version =
_updateInformation->next_version =
base::SysNSStringToUTF8([attributeDict valueForKey:@"version"]);
} else {
_hasError = YES;
......@@ -345,6 +347,26 @@ void OmahaService::Start(std::unique_ptr<network::PendingSharedURLLoaderFactory>
base::Unretained(service)));
}
// static
void OmahaService::CheckNow(OneOffCallback callback) {
DCHECK(!callback.is_null());
OmahaService* service = GetInstance();
DCHECK(service->started_);
DCHECK(service->one_off_check_callback_.is_null());
service->one_off_check_callback_ = std::move(callback);
// If there is not an ongoing ping, send one.
if (!service->url_loader_) {
service->SendPing();
} else {
// The one off ping is taking the scheduled one, so the scheduled ping is
// now "canceled".
service->scheduled_ping_canceled_ = true;
}
}
// static
void OmahaService::Stop() {
if (!OmahaService::IsEnabled()) {
......@@ -589,6 +611,12 @@ std::string OmahaService::GetCurrentPingContent() {
}
void OmahaService::SendPing() {
// If a scheduled ping comes during a one off, drop it.
if (url_loader_ && !one_off_check_callback_.is_null()) {
scheduled_ping_canceled_ = true;
return;
}
// Check that no request is in progress.
DCHECK(!url_loader_);
......@@ -711,13 +739,29 @@ void OmahaService::OnURLLoadComplete(
last_server_date_ = [delegate serverDate];
ClearInstallRetryRequestId();
PersistStates();
SendOrScheduleNextPing();
bool need_to_schedule_ping = true;
// Send notification for updates if needed.
UpgradeRecommendedDetails* details = [delegate upgradeRecommendedDetails];
if (details) {
base::PostTask(FROM_HERE, {web::WebThread::UI},
base::BindOnce(upgrade_recommended_callback_, *details));
// Use the correct callback based on if a one-off check is ongoing.
if (!one_off_check_callback_.is_null()) {
base::PostTask(
FROM_HERE, {web::WebThread::UI},
base::BindOnce(std::move(one_off_check_callback_), *details));
// Do not schedule another ping for one-off checks, unless
// it canceled a scheduled ping.
need_to_schedule_ping = scheduled_ping_canceled_;
scheduled_ping_canceled_ = false;
} else {
base::PostTask(FROM_HERE, {web::WebThread::UI},
base::BindOnce(upgrade_recommended_callback_, *details));
}
}
// Schedule next ping if necessary.
if (need_to_schedule_ping) {
SendOrScheduleNextPing();
}
}
......
......@@ -18,6 +18,7 @@
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state_manager.h"
#include "ios/chrome/browser/install_time_util.h"
#include "ios/chrome/browser/upgrade/upgrade_recommended_details.h"
#include "ios/chrome/common/channel_info.h"
#include "ios/chrome/test/ios_chrome_scoped_testing_chrome_browser_state_manager.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
......@@ -58,9 +59,17 @@ class OmahaServiceTest : public PlatformTest {
}
void OnNeedUpdate(const UpgradeRecommendedDetails& details) {
need_update_ = true;
was_one_off_ = false;
need_update_ = !details.is_up_to_date;
}
void OneOffCheck(const UpgradeRecommendedDetails& details) {
was_one_off_ = true;
need_update_ = !details.is_up_to_date;
}
bool WasOneOff() { return was_one_off_; }
bool NeedUpdate() {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
if (!need_update_) {
......@@ -69,6 +78,16 @@ class OmahaServiceTest : public PlatformTest {
return need_update_;
}
const std::string GetResponseSuccess() {
return std::string("<?xml version=\"1.0\"?><response protocol=\"3.0\" "
"server=\"prod\">"
"<daystart elapsed_days=\"4088\"/><app appid=\"") +
test_application_id() +
"\" status=\"ok\">"
"<updatecheck status=\"noupdate\"/><ping status=\"ok\"/>"
"</app></response>";
}
void CleanService(OmahaService* service,
const std::string& last_sent_version) {
service->ClearInstallRetryRequestId();
......@@ -94,7 +113,8 @@ class OmahaServiceTest : public PlatformTest {
test_shared_url_loader_factory_;
private:
bool need_update_;
bool need_update_ = false;
bool was_one_off_ = false;
IOSChromeScopedTestingChromeBrowserStateManager scoped_browser_state_manager_;
web::WebTaskEnvironment task_environment_;
......@@ -227,17 +247,43 @@ TEST_F(OmahaServiceTest, SendPingSuccess) {
EXPECT_GE(service.next_tries_time_, now + base::TimeDelta::FromMinutes(54));
EXPECT_LE(service.next_tries_time_, now + base::TimeDelta::FromHours(7));
std::string response =
std::string(
"<?xml version=\"1.0\"?><response protocol=\"3.0\" server=\"prod\">"
"<daystart elapsed_days=\"4088\"/><app appid=\"") +
test_application_id() +
"\" status=\"ok\">"
"<updatecheck status=\"noupdate\"/><ping status=\"ok\"/>"
"</app></response>";
auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
test_url_loader_factory_.SimulateResponseForPendingRequest(
pending_request->request.url.spec(), response);
pending_request->request.url.spec(), GetResponseSuccess());
EXPECT_EQ(0, service.number_of_tries_);
EXPECT_FALSE(service.current_ping_time_.is_null());
EXPECT_EQ(service.current_ping_time_, service.next_tries_time_);
EXPECT_GT(service.last_sent_time_, now);
EXPECT_EQ(4088, service.last_server_date_);
EXPECT_FALSE(NeedUpdate());
}
TEST_F(OmahaServiceTest, OneOffSuccess) {
base::Time now = base::Time::Now();
OmahaService service(false);
service.StartInternal();
service.set_upgrade_recommended_callback(
base::Bind(&OmahaServiceTest::OnNeedUpdate, base::Unretained(this)));
service.InitializeURLLoaderFactory(test_shared_url_loader_factory_);
service.one_off_check_callback_ =
base::BindOnce(^(UpgradeRecommendedDetails details) {
OmahaServiceTest::OneOffCheck(details);
});
CleanService(&service, version_info::GetVersionNumber());
service.SendPing();
EXPECT_EQ(1, service.number_of_tries_);
EXPECT_TRUE(service.current_ping_time_.is_null());
EXPECT_GE(service.next_tries_time_, now + base::TimeDelta::FromMinutes(54));
EXPECT_LE(service.next_tries_time_, now + base::TimeDelta::FromHours(7));
auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
test_url_loader_factory_.SimulateResponseForPendingRequest(
pending_request->request.url.spec(), GetResponseSuccess());
EXPECT_EQ(0, service.number_of_tries_);
EXPECT_FALSE(service.current_ping_time_.is_null());
......@@ -245,6 +291,131 @@ TEST_F(OmahaServiceTest, SendPingSuccess) {
EXPECT_GT(service.last_sent_time_, now);
EXPECT_EQ(4088, service.last_server_date_);
EXPECT_FALSE(NeedUpdate());
EXPECT_TRUE(WasOneOff());
}
TEST_F(OmahaServiceTest, OngoingPingOneOffCallbackUsed) {
base::Time now = base::Time::Now();
OmahaService service(false);
service.StartInternal();
service.set_upgrade_recommended_callback(
base::Bind(&OmahaServiceTest::OnNeedUpdate, base::Unretained(this)));
service.InitializeURLLoaderFactory(test_shared_url_loader_factory_);
CleanService(&service, version_info::GetVersionNumber());
service.SendPing();
EXPECT_EQ(1, service.number_of_tries_);
EXPECT_TRUE(service.current_ping_time_.is_null());
EXPECT_GE(service.next_tries_time_, now + base::TimeDelta::FromMinutes(54));
EXPECT_LE(service.next_tries_time_, now + base::TimeDelta::FromHours(7));
// One off callback set during ongoing ping, it should now be used for
// response.
service.one_off_check_callback_ =
base::BindOnce(^(UpgradeRecommendedDetails details) {
OmahaServiceTest::OneOffCheck(details);
});
auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
test_url_loader_factory_.SimulateResponseForPendingRequest(
pending_request->request.url.spec(), GetResponseSuccess());
EXPECT_EQ(0, service.number_of_tries_);
EXPECT_FALSE(service.current_ping_time_.is_null());
EXPECT_EQ(service.current_ping_time_, service.next_tries_time_);
EXPECT_GT(service.last_sent_time_, now);
EXPECT_EQ(4088, service.last_server_date_);
EXPECT_FALSE(NeedUpdate());
EXPECT_TRUE(WasOneOff());
}
TEST_F(OmahaServiceTest, OneOffCallbackUsedOnlyOnce) {
base::Time now = base::Time::Now();
OmahaService service(false);
service.StartInternal();
service.set_upgrade_recommended_callback(
base::Bind(&OmahaServiceTest::OnNeedUpdate, base::Unretained(this)));
service.InitializeURLLoaderFactory(test_shared_url_loader_factory_);
service.one_off_check_callback_ =
base::BindOnce(^(UpgradeRecommendedDetails details) {
OmahaServiceTest::OneOffCheck(details);
});
CleanService(&service, version_info::GetVersionNumber());
service.SendPing();
EXPECT_EQ(1, service.number_of_tries_);
EXPECT_TRUE(service.current_ping_time_.is_null());
EXPECT_GE(service.next_tries_time_, now + base::TimeDelta::FromMinutes(54));
EXPECT_LE(service.next_tries_time_, now + base::TimeDelta::FromHours(7));
auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
test_url_loader_factory_.SimulateResponseForPendingRequest(
pending_request->request.url.spec(), GetResponseSuccess());
EXPECT_EQ(0, service.number_of_tries_);
EXPECT_FALSE(service.current_ping_time_.is_null());
EXPECT_EQ(service.current_ping_time_, service.next_tries_time_);
EXPECT_GT(service.last_sent_time_, now);
EXPECT_EQ(4088, service.last_server_date_);
EXPECT_FALSE(NeedUpdate());
EXPECT_TRUE(WasOneOff());
service.SendPing();
pending_request = test_url_loader_factory_.GetPendingRequest(0);
test_url_loader_factory_.SimulateResponseForPendingRequest(
pending_request->request.url.spec(), GetResponseSuccess());
EXPECT_FALSE(NeedUpdate());
EXPECT_FALSE(WasOneOff());
}
TEST_F(OmahaServiceTest, ScheduledPingDuringOneOffDropped) {
base::Time now = base::Time::Now();
OmahaService service(false);
service.StartInternal();
service.set_upgrade_recommended_callback(
base::Bind(&OmahaServiceTest::OnNeedUpdate, base::Unretained(this)));
service.InitializeURLLoaderFactory(test_shared_url_loader_factory_);
service.one_off_check_callback_ =
base::BindOnce(^(UpgradeRecommendedDetails details) {
OmahaServiceTest::OneOffCheck(details);
});
CleanService(&service, version_info::GetVersionNumber());
service.SendPing();
EXPECT_EQ(1, service.number_of_tries_);
EXPECT_TRUE(service.current_ping_time_.is_null());
EXPECT_GE(service.next_tries_time_, now + base::TimeDelta::FromMinutes(54));
EXPECT_LE(service.next_tries_time_, now + base::TimeDelta::FromHours(7));
service.SendPing();
// Ping during one-off should be dropped, nothing should change.
EXPECT_EQ(1, service.number_of_tries_);
EXPECT_TRUE(service.current_ping_time_.is_null());
EXPECT_GE(service.next_tries_time_, now + base::TimeDelta::FromMinutes(54));
EXPECT_LE(service.next_tries_time_, now + base::TimeDelta::FromHours(7));
auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
test_url_loader_factory_.SimulateResponseForPendingRequest(
pending_request->request.url.spec(), GetResponseSuccess());
EXPECT_EQ(0, service.number_of_tries_);
EXPECT_FALSE(service.current_ping_time_.is_null());
EXPECT_EQ(service.current_ping_time_, service.next_tries_time_);
EXPECT_GT(service.last_sent_time_, now);
EXPECT_EQ(4088, service.last_server_date_);
EXPECT_FALSE(NeedUpdate());
EXPECT_TRUE(WasOneOff());
}
TEST_F(OmahaServiceTest, ParseAndEchoLastServerDate) {
......@@ -258,17 +429,9 @@ TEST_F(OmahaServiceTest, ParseAndEchoLastServerDate) {
service.SendPing();
std::string response =
std::string(
"<?xml version=\"1.0\"?><response protocol=\"3.0\" server=\"prod\">"
"<daystart elapsed_days=\"4088\"/><app appid=\"") +
test_application_id() +
"\" status=\"ok\">"
"<updatecheck status=\"noupdate\"/><ping status=\"ok\"/>"
"</app></response>";
auto* pending_request = test_url_loader_factory_.GetPendingRequest(0);
test_url_loader_factory_.SimulateResponseForPendingRequest(
pending_request->request.url.spec(), response);
pending_request->request.url.spec(), GetResponseSuccess());
EXPECT_EQ(4088, service.last_server_date_);
......
......@@ -55,11 +55,6 @@
namespace {
// The user defaults key for the last time the update infobar was shown.
NSString* const kLastInfobarDisplayTimeKey = @"UpdateInfobarLastDisplayTime";
// The amount of time that must elapse before showing the infobar again.
const NSTimeInterval kInfobarDisplayInterval = 24 * 60 * 60; // One day.
// The class controlling the look of the infobar displayed when an upgrade is
// available.
class UpgradeInfoBarDelegate : public ConfirmInfoBarDelegate {
......@@ -264,8 +259,8 @@ class UpgradeInfoBarDismissObserver
NSDate* lastDisplay = [defaults objectForKey:kLastInfobarDisplayTimeKey];
// Absolute value is to ensure the infobar won't be suppressed forever if the
// clock temporarily jumps to the distant future.
if (lastDisplay &&
fabs([lastDisplay timeIntervalSinceNow]) < kInfobarDisplayInterval) {
if (lastDisplay && fabs([lastDisplay timeIntervalSinceNow]) <
kInfobarDisplayIntervalInSeconds) {
return YES;
}
return NO;
......@@ -435,6 +430,7 @@ class UpgradeInfoBarDismissObserver
[defaults setValue:base::SysUTF8ToNSString(upgradeUrl.spec())
forKey:kIOSChromeUpgradeURLKey];
[defaults setValue:newVersionString forKey:kIOSChromeNextVersionKey];
[defaults setBool:details.is_up_to_date forKey:kIOSChromeUpToDateKey];
if ([self shouldShowInfoBar])
[self showUpgradeInfoBars];
......@@ -446,12 +442,13 @@ class UpgradeInfoBarDismissObserver
[defaults removeObjectForKey:kIOSChromeNextVersionKey];
[defaults removeObjectForKey:kIOSChromeUpgradeURLKey];
[defaults removeObjectForKey:kLastInfobarDisplayTimeKey];
[defaults removeObjectForKey:kIOSChromeUpToDateKey];
[_clients removeAllObjects];
}
- (void)setLastDisplayToPast {
NSDate* pastDate =
[NSDate dateWithTimeIntervalSinceNow:-(kInfobarDisplayInterval + 1)];
NSDate* pastDate = [NSDate
dateWithTimeIntervalSinceNow:-(kInfobarDisplayIntervalInSeconds + 1)];
[[NSUserDefaults standardUserDefaults] setObject:pastDate
forKey:kLastInfobarDisplayTimeKey];
}
......
......@@ -11,5 +11,11 @@
extern NSString* const kIOSChromeNextVersionKey;
// The user defaults key for the upgrade URL.
extern NSString* const kIOSChromeUpgradeURLKey;
// The user defaults key for up to date status;
extern NSString* const kIOSChromeUpToDateKey;
// The user defaults key for the last time the update infobar was shown.
extern NSString* const kLastInfobarDisplayTimeKey;
// The amount of time that must elapse before showing the infobar again.
extern const NSTimeInterval kInfobarDisplayIntervalInSeconds;
#endif // IOS_CHROME_BROWSER_UPGRADE_UPGRADE_CONSTANTS_H_
......@@ -10,3 +10,7 @@
NSString* const kIOSChromeNextVersionKey = @"UpdateInfobarUpgradeURL";
NSString* const kIOSChromeUpgradeURLKey = @"UpdateInfobarNextVersion";
NSString* const kIOSChromeUpToDateKey = @"UpdateInfobarIsUpToDate";
NSString* const kLastInfobarDisplayTimeKey = @"UpdateInfobarLastDisplayTime";
const NSTimeInterval kInfobarDisplayIntervalInSeconds =
24 * 60 * 60; // One day.
......@@ -12,6 +12,7 @@
struct UpgradeRecommendedDetails {
GURL upgrade_url;
std::string next_version;
bool is_up_to_date = false;
};
#endif // IOS_CHROME_BROWSER_UPGRADE_UPGRADE_RECOMMENDED_DETAILS_H_
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