Index: chrome/browser/chrome_to_mobile_service.cc |
=================================================================== |
--- chrome/browser/chrome_to_mobile_service.cc (revision 152612) |
+++ chrome/browser/chrome_to_mobile_service.cc (working copy) |
@@ -11,15 +11,15 @@ |
#include "base/json/json_reader.h" |
#include "base/json/json_writer.h" |
#include "base/metrics/histogram.h" |
+#include "base/stringprintf.h" |
#include "base/utf_string_conversions.h" |
#include "chrome/app/chrome_command_ids.h" |
+#include "chrome/browser/content_settings/cookie_settings.h" |
#include "chrome/browser/prefs/pref_service.h" |
#include "chrome/browser/printing/cloud_print/cloud_print_url.h" |
#include "chrome/browser/profiles/profile.h" |
#include "chrome/browser/signin/token_service.h" |
#include "chrome/browser/signin/token_service_factory.h" |
-#include "chrome/browser/sync/profile_sync_service.h" |
-#include "chrome/browser/sync/profile_sync_service_factory.h" |
#include "chrome/browser/ui/browser.h" |
#include "chrome/browser/ui/browser_command_controller.h" |
#include "chrome/browser/ui/browser_finder.h" |
@@ -38,13 +38,10 @@ |
#include "content/public/browser/notification_details.h" |
#include "content/public/browser/notification_source.h" |
#include "content/public/browser/web_contents.h" |
-#include "google/cacheinvalidation/include/types.h" |
-#include "google/cacheinvalidation/types.pb.h" |
#include "net/base/escape.h" |
#include "net/base/load_flags.h" |
#include "net/url_request/url_fetcher.h" |
#include "net/url_request/url_request_context_getter.h" |
-#include "sync/notifier/invalidation_util.h" |
namespace { |
@@ -62,16 +59,19 @@ |
// Note that this limitation does not hold across application restarts. |
const int kSearchRequestDelayHours = 24; |
-// The sync invalidation object ID for Chrome to Mobile's mobile device list. |
-// This corresponds with cloud print's server-side invalidation object ID. |
-// Meaning: "U" == "User", "CM" == "Chrome to Mobile", "MLST" == "Mobile LiST". |
-const char kSyncInvalidationObjectIdChromeToMobileDeviceList[] = "UCMMLST"; |
- |
// The cloud print OAuth2 scope and 'printer' type of compatible mobile devices. |
const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint"; |
const char kTypeAndroid[] = "ANDROID_CHROME_SNAPSHOT"; |
const char kTypeIOS[] = "IOS_CHROME_SNAPSHOT"; |
+// The account info URL pattern and strings to check for cloud print access. |
+// The 'key=' query parameter is used for caching; supply a random number. |
+// The 'rv=2' query parameter requests a JSON response; use 'rv=1' for XML. |
+const char kAccountInfoURL[] = |
+ "https://clients1.google.com/tbproxy/getaccountinfo?key=%s&rv=2&%s"; |
+const char kAccountServicesKey[] = "services"; |
+const char kCloudPrintSerivceValue[] = "cprt"; |
+ |
// The Chrome To Mobile requestor type; used by services for filtering. |
const char kChromeToMobileRequestor[] = "requestor=chrome-to-mobile"; |
@@ -125,8 +125,8 @@ |
} |
// Get the URL for cloud print device search; appends a requestor query param. |
-GURL GetSearchURL(const GURL& cloud_print_url) { |
- GURL search_url = cloud_print::GetUrlForSearch(cloud_print_url); |
+GURL GetSearchURL(const GURL& service_url) { |
+ GURL search_url = cloud_print::GetUrlForSearch(service_url); |
GURL::Replacements replacements; |
std::string query(kChromeToMobileRequestor); |
replacements.SetQueryStr(query); |
@@ -180,50 +180,46 @@ |
void ChromeToMobileService::RegisterUserPrefs(PrefService* prefs) { |
prefs->RegisterListPref(prefs::kChromeToMobileDeviceList, |
PrefService::UNSYNCABLE_PREF); |
+ prefs->RegisterInt64Pref(prefs::kChromeToMobileTimestamp, 0, |
+ PrefService::UNSYNCABLE_PREF); |
} |
ChromeToMobileService::ChromeToMobileService(Profile* profile) |
: ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), |
profile_(profile), |
- sync_invalidation_enabled_(false) { |
- // TODO(msw): Unit tests do not provide profiles; see http://crbug.com/122183 |
- ProfileSyncService* profile_sync_service = |
- profile_ ? ProfileSyncServiceFactory::GetForProfile(profile_) : NULL; |
- if (profile_sync_service) { |
- CloudPrintURL cloud_print_url(profile_); |
- cloud_print_url_ = cloud_print_url.GetCloudPrintServiceURL(); |
- // Register for cloud print device list invalidation notifications. |
- // TODO(msw|akalin): Initialize |sync_invalidation_enabled_| properly. |
- profile_sync_service->RegisterInvalidationHandler(this); |
- syncer::ObjectIdSet ids; |
- ids.insert(invalidation::ObjectId( |
- ipc::invalidation::ObjectSource::CHROME_COMPONENTS, |
- kSyncInvalidationObjectIdChromeToMobileDeviceList)); |
- profile_sync_service->UpdateRegisteredInvalidationIds(this, ids); |
+ cloud_print_url_(new CloudPrintURL(profile)), |
+ cloud_print_accessible_(false) { |
+ // TODO(msw): Fix GMock tests, which lack profiles (http://crbug.com/122183). |
+ if (profile_) { |
+ // Get an access token as soon as the Gaia login refresh token is available. |
+ TokenService* service = TokenServiceFactory::GetForProfile(profile_); |
+ registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, |
+ content::Source<TokenService>(service)); |
+ if (service->HasOAuthLoginToken()) |
+ RequestAccessToken(); |
} |
} |
ChromeToMobileService::~ChromeToMobileService() { |
while (!snapshots_.empty()) |
DeleteSnapshot(*snapshots_.begin()); |
- // TODO(msw): Unit tests do not provide profiles; see http://crbug.com/122183 |
- // Unregister for cloud print device list invalidation notifications. |
- ProfileSyncService* profile_sync_service = |
- profile_ ? ProfileSyncServiceFactory::GetForProfile(profile_) : NULL; |
- if (profile_sync_service) |
- profile_sync_service->UnregisterInvalidationHandler(this); |
} |
bool ChromeToMobileService::HasMobiles() const { |
- const base::ListValue* mobiles = GetMobiles(); |
- return mobiles && !mobiles->empty(); |
+ return !GetMobiles()->empty(); |
} |
const base::ListValue* ChromeToMobileService::GetMobiles() const { |
- return sync_invalidation_enabled_ ? |
- profile_->GetPrefs()->GetList(prefs::kChromeToMobileDeviceList) : NULL; |
+ return profile_->GetPrefs()->GetList(prefs::kChromeToMobileDeviceList); |
} |
+void ChromeToMobileService::RequestMobileListUpdate() { |
+ if (access_token_.empty()) |
+ RequestAccessToken(); |
+ else if (cloud_print_accessible_) |
+ RequestDeviceSearch(); |
+} |
+ |
void ChromeToMobileService::GenerateSnapshot(Browser* browser, |
base::WeakPtr<Observer> observer) { |
// Callback SnapshotFileCreated from CreateSnapshotFile to continue. |
@@ -238,28 +234,19 @@ |
} |
} |
-void ChromeToMobileService::SendToMobile(const base::DictionaryValue* mobile, |
+void ChromeToMobileService::SendToMobile(const base::DictionaryValue& mobile, |
const FilePath& snapshot, |
Browser* browser, |
base::WeakPtr<Observer> observer) { |
- if (access_token_.empty()) { |
- // Enqueue this task to perform after obtaining an access token. |
- task_queue_.push(base::Bind(&ChromeToMobileService::SendToMobile, |
- weak_ptr_factory_.GetWeakPtr(), base::Owned(mobile->DeepCopy()), |
- snapshot, browser, observer)); |
- RequestAccessToken(); |
- return; |
- } |
- |
LogMetric(SENDING_URL); |
JobData data; |
std::string mobile_os; |
- if (!mobile->GetString("type", &mobile_os)) |
+ if (!mobile.GetString("type", &mobile_os)) |
NOTREACHED(); |
data.mobile_os = (mobile_os.compare(kTypeAndroid) == 0) ? |
ChromeToMobileService::ANDROID : ChromeToMobileService::IOS; |
- if (!mobile->GetString("id", &data.mobile_id)) |
+ if (!mobile.GetString("id", &data.mobile_id)) |
NOTREACHED(); |
content::WebContents* web_contents = chrome::GetActiveWebContents(browser); |
data.url = web_contents->GetURL(); |
@@ -314,9 +301,12 @@ |
chrome::Navigate(¶ms); |
} |
-void ChromeToMobileService::OnURLFetchComplete(const net::URLFetcher* source) { |
- if (source->GetURL() == GetSearchURL(cloud_print_url_)) |
- HandleSearchResponse(source); |
+void ChromeToMobileService::OnURLFetchComplete( |
+ const net::URLFetcher* source) { |
+ if (source == account_info_request_.get()) |
+ HandleAccountInfoResponse(); |
+ else if (source == search_request_.get()) |
+ HandleSearchResponse(); |
else |
HandleSubmitResponse(source); |
} |
@@ -328,9 +318,8 @@ |
DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_AVAILABLE); |
TokenService::TokenAvailableDetails* token_details = |
content::Details<TokenService::TokenAvailableDetails>(details).ptr(); |
- // Invalidate the cloud print access token on Gaia login token updates. |
if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken) |
- access_token_.clear(); |
+ RequestAccessToken(); |
} |
void ChromeToMobileService::OnGetTokenSuccess( |
@@ -340,57 +329,18 @@ |
access_token_fetcher_.reset(); |
auth_retry_timer_.Stop(); |
access_token_ = access_token; |
- |
- while (!task_queue_.empty()) { |
- // Post all tasks that were queued and waiting on a valid access token. |
- if (!content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
- task_queue_.front())) { |
- NOTREACHED(); |
- } |
- task_queue_.pop(); |
- } |
+ RequestAccountInfo(); |
} |
void ChromeToMobileService::OnGetTokenFailure( |
const GoogleServiceAuthError& error) { |
access_token_fetcher_.reset(); |
auth_retry_timer_.Stop(); |
- |
auth_retry_timer_.Start( |
FROM_HERE, base::TimeDelta::FromHours(kAuthRetryDelayHours), |
this, &ChromeToMobileService::RequestAccessToken); |
} |
-void ChromeToMobileService::OnNotificationsEnabled() { |
- sync_invalidation_enabled_ = true; |
- UpdateCommandState(); |
-} |
- |
-void ChromeToMobileService::OnNotificationsDisabled( |
- syncer::NotificationsDisabledReason reason) { |
- sync_invalidation_enabled_ = false; |
- UpdateCommandState(); |
-} |
- |
-void ChromeToMobileService::OnIncomingNotification( |
- const syncer::ObjectIdPayloadMap& id_payloads, |
- syncer::IncomingNotificationSource source) { |
- DCHECK_EQ(id_payloads.size(), 1U); |
- DCHECK_EQ(id_payloads.count(invalidation::ObjectId( |
- ipc::invalidation::ObjectSource::CHROME_COMPONENTS, |
- kSyncInvalidationObjectIdChromeToMobileDeviceList)), 1U); |
- RequestDeviceSearch(); |
-} |
- |
-const std::string& ChromeToMobileService::GetAccessTokenForTest() const { |
- return access_token_; |
-} |
- |
-void ChromeToMobileService::SetAccessTokenForTest( |
- const std::string& access_token) { |
- access_token_ = access_token; |
-} |
- |
void ChromeToMobileService::UpdateCommandState() const { |
// Ensure the feature is not disabled by commandline options. |
DCHECK(IsChromeToMobileEnabled()); |
@@ -424,9 +374,9 @@ |
} |
net::URLFetcher* ChromeToMobileService::CreateRequest(const JobData& data) { |
+ const GURL service_url(cloud_print_url_->GetCloudPrintServiceURL()); |
net::URLFetcher* request = net::URLFetcher::Create( |
- cloud_print::GetUrlForSubmit(cloud_print_url_), |
- net::URLFetcher::POST, this); |
+ cloud_print::GetUrlForSubmit(service_url), net::URLFetcher::POST, this); |
InitRequest(request); |
return request; |
} |
@@ -485,13 +435,8 @@ |
} |
void ChromeToMobileService::RequestAccessToken() { |
- // Register to observe Gaia login refresh token updates. |
+ // Deny concurrent requests and bail without a valid Gaia login refresh token. |
TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); |
- if (registrar_.IsEmpty()) |
- registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, |
- content::Source<TokenService>(token_service)); |
- |
- // Deny concurrent requests and bail without a valid Gaia login refresh token. |
if (access_token_fetcher_.get() || !token_service->HasOAuthLoginToken()) |
return; |
@@ -505,33 +450,86 @@ |
std::vector<std::string>(1, kCloudPrintAuth)); |
} |
-void ChromeToMobileService::RequestDeviceSearch() { |
- DCHECK(sync_invalidation_enabled_); |
- if (access_token_.empty()) { |
- // Enqueue this task to perform after obtaining an access token. |
- task_queue_.push(base::Bind(&ChromeToMobileService::RequestDeviceSearch, |
- weak_ptr_factory_.GetWeakPtr())); |
- RequestAccessToken(); |
+void ChromeToMobileService::RequestAccountInfo() { |
+ // Deny concurrent requests. |
+ if (account_info_request_.get()) |
return; |
+ |
+ std::string url_string = StringPrintf(kAccountInfoURL, |
+ base::GenerateGUID().c_str(), kChromeToMobileRequestor); |
+ GURL url(url_string); |
+ |
+ // Account information is read from the profile's cookie. If cookies are |
+ // blocked, access cloud print directly to list any potential devices. |
+ scoped_refptr<CookieSettings> cookie_settings = |
+ CookieSettings::Factory::GetForProfile(profile_); |
+ if (cookie_settings && !cookie_settings->IsReadingCookieAllowed(url, url)) { |
+ cloud_print_accessible_ = true; |
+ RequestMobileListUpdate(); |
+ return; |
} |
+ account_info_request_.reset( |
+ net::URLFetcher::Create(url, net::URLFetcher::GET, this)); |
+ account_info_request_->SetRequestContext(profile_->GetRequestContext()); |
+ account_info_request_->SetMaxRetries(kMaxRetries); |
+ // This request sends the user's cookie to check the cloud print service flag. |
+ account_info_request_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); |
+ account_info_request_->Start(); |
+} |
+ |
+void ChromeToMobileService::RequestDeviceSearch() { |
+ // Deny requests if cloud print is inaccessible, and deny concurrent requests. |
+ if (!cloud_print_accessible_ || search_request_.get()) |
+ return; |
+ |
+ PrefService* prefs = profile_->GetPrefs(); |
+ base::TimeTicks previous_search_time = base::TimeTicks::FromInternalValue( |
+ prefs->GetInt64(prefs::kChromeToMobileTimestamp)); |
+ |
+ // Deny requests before the delay period has passed since the last request. |
+ base::TimeDelta elapsed_time = base::TimeTicks::Now() - previous_search_time; |
+ if (!previous_search_time.is_null() && |
+ elapsed_time.InHours() < kSearchRequestDelayHours) |
+ return; |
+ |
LogMetric(DEVICES_REQUESTED); |
- net::URLFetcher* search_request = net::URLFetcher::Create( |
- GetSearchURL(cloud_print_url_), net::URLFetcher::GET, this); |
- InitRequest(search_request); |
- search_request->Start(); |
+ const GURL service_url(cloud_print_url_->GetCloudPrintServiceURL()); |
+ search_request_.reset(net::URLFetcher::Create(GetSearchURL(service_url), |
+ net::URLFetcher::GET, this)); |
+ InitRequest(search_request_.get()); |
+ search_request_->Start(); |
} |
-void ChromeToMobileService::HandleSearchResponse( |
- const net::URLFetcher* source) { |
+void ChromeToMobileService::HandleAccountInfoResponse() { |
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
- DCHECK_EQ(source->GetURL(), GetSearchURL(cloud_print_url_)); |
std::string data; |
+ account_info_request_->GetResponseAsString(&data); |
+ account_info_request_.reset(); |
+ |
+ ListValue* services = NULL; |
+ DictionaryValue* dictionary = NULL; |
+ scoped_ptr<Value> json(base::JSONReader::Read(data)); |
+ StringValue cloud_print_service(kCloudPrintSerivceValue); |
+ if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && |
+ dictionary->GetList(kAccountServicesKey, &services) && services && |
+ services->Find(cloud_print_service) != services->end()) { |
+ cloud_print_accessible_ = true; |
+ RequestMobileListUpdate(); |
+ } |
+} |
+ |
+void ChromeToMobileService::HandleSearchResponse() { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
+ |
+ std::string data; |
+ search_request_->GetResponseAsString(&data); |
+ search_request_.reset(); |
+ |
ListValue* list = NULL; |
DictionaryValue* dictionary = NULL; |
- source->GetResponseAsString(&data); |
scoped_ptr<Value> json(base::JSONReader::Read(data)); |
if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && |
dictionary->GetList(cloud_print::kPrinterListValue, &list)) { |
@@ -557,8 +555,11 @@ |
} |
} |
- // Update the cached mobile device list in profile prefs. |
- profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); |
+ // Update the mobile list and timestamp in prefs. |
+ PrefService* prefs = profile_->GetPrefs(); |
+ prefs->Set(prefs::kChromeToMobileDeviceList, mobiles); |
+ prefs->SetInt64(prefs::kChromeToMobileTimestamp, |
+ base::TimeTicks::Now().ToInternalValue()); |
if (HasMobiles()) |
LogMetric(DEVICES_AVAILABLE); |