|  | // Copyright 2013 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. | 
|  |  | 
|  | #import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h" | 
|  |  | 
|  | #import <CoreLocation/CoreLocation.h> | 
|  | #import <UIKit/UIKit.h> | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #import "base/ios/weak_nsobject.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/mac/scoped_nsobject.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/version.h" | 
|  | #include "components/google/core/browser/google_util.h" | 
|  | #include "components/version_info/version_info.h" | 
|  | #include "ios/chrome/browser/browser_state/chrome_browser_state.h" | 
|  | #import "ios/chrome/browser/geolocation/CLLocation+OmniboxGeolocation.h" | 
|  | #import "ios/chrome/browser/geolocation/CLLocation+XGeoHeader.h" | 
|  | #import "ios/chrome/browser/geolocation/location_manager.h" | 
|  | #import "ios/chrome/browser/geolocation/omnibox_geolocation_authorization_alert.h" | 
|  | #import "ios/chrome/browser/geolocation/omnibox_geolocation_config.h" | 
|  | #import "ios/chrome/browser/geolocation/omnibox_geolocation_controller+Testing.h" | 
|  | #import "ios/chrome/browser/geolocation/omnibox_geolocation_local_state.h" | 
|  | #import "ios/chrome/browser/tabs/tab.h" | 
|  | #include "ios/web/public/navigation_item.h" | 
|  | #import "ios/web/public/navigation_manager.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Values for the histogram that records whether we sent the X-Geo header for | 
|  | // an Omnibox query or why we did not do so. These match the definition of | 
|  | // GeolocationHeaderSentOrNot in Chromium | 
|  | // src-internal/tools/histograms/histograms.xml. | 
|  | typedef enum { | 
|  | // The user disabled location for Google.com (not used by Chrome iOS). | 
|  | kHeaderStateNotSentAuthorizationGoogleDenied = 0, | 
|  | // The user has not yet determined Chrome's access to the current device | 
|  | // location or Chrome's use of geolocation for Omnibox queries. | 
|  | kHeaderStateNotSentAuthorizationNotDetermined, | 
|  | // The current device location is not available. | 
|  | kHeaderStateNotSentLocationNotAvailable, | 
|  | // The current device location is stale. | 
|  | kHeaderStateNotSentLocationStale, | 
|  | // The X-Geo header was sent. | 
|  | kHeaderStateSent, | 
|  | // The user denied Chrome from accessing the current device location. | 
|  | kHeaderStateNotSentAuthorizationChromeDenied, | 
|  | // The user denied Chrome from using geolocation for Omnibox queries. | 
|  | kHeaderStateNotSentAuthorizationOmniboxDenied, | 
|  | // The user's Google search domain is not whitelisted. | 
|  | kHeaderStateNotSentDomainNotWhitelisted, | 
|  | // The number of possible of HeaderState values to report. | 
|  | kHeaderStateCount, | 
|  | } HeaderState; | 
|  |  | 
|  | // Values for the histograms that record the user's action when prompted to | 
|  | // authorize the use of location by Chrome. These match the definition of | 
|  | // GeolocationAuthorizationAction in Chromium | 
|  | // src-internal/tools/histograms/histograms.xml. | 
|  | typedef enum { | 
|  | // The user authorized use of location. | 
|  | kAuthorizationActionAuthorized = 0, | 
|  | // The user permanently denied use of location (Don't Allow). | 
|  | kAuthorizationActionPermanentlyDenied, | 
|  | // The user denied use of location at this prompt (Not Now). | 
|  | kAuthorizationActionDenied, | 
|  | // The number of possible AuthorizationAction values to report. | 
|  | kAuthorizationActionCount, | 
|  | } AuthorizationAction; | 
|  |  | 
|  | // Name of the histogram recording HeaderState. | 
|  | const char* const kGeolocationHeaderSentOrNotHistogram = | 
|  | "Geolocation.HeaderSentOrNot"; | 
|  |  | 
|  | // Name of the histogram recording location acquisition time. | 
|  | const char* const kOmniboxQueryGeolocationAcquisitionTimeHistogram = | 
|  | "Omnibox.QueryGeolocationAcquisitionTime"; | 
|  |  | 
|  | // Name of the histogram recording estimated location accuracy. | 
|  | const char* const kOmniboxQueryGeolocationHorizontalAccuracyHistogram = | 
|  | "Omnibox.QueryGeolocationHorizontalAccuracy"; | 
|  |  | 
|  | // Name of the histogram recording AuthorizationAction for an existing user. | 
|  | const char* const kGeolocationAuthorizationActionExistingUser = | 
|  | "Geolocation.AuthorizationActionExistingUser"; | 
|  |  | 
|  | // Name of the histogram recording AuthorizationAction for a new user. | 
|  | const char* const kGeolocationAuthorizationActionNewUser = | 
|  | "Geolocation.AuthorizationActionNewUser"; | 
|  |  | 
|  | }  // anonymous namespace | 
|  |  | 
|  | @interface OmniboxGeolocationController ()< | 
|  | LocationManagerDelegate, | 
|  | OmniboxGeolocationAuthorizationAlertDelegate> { | 
|  | base::scoped_nsobject<OmniboxGeolocationLocalState> localState_; | 
|  | base::scoped_nsobject<LocationManager> locationManager_; | 
|  | base::scoped_nsobject<OmniboxGeolocationAuthorizationAlert> | 
|  | authorizationAlert_; | 
|  | base::WeakNSObject<Tab> weakTabToReload_; | 
|  |  | 
|  | // Records whether we have deliberately presented the system prompt, so that | 
|  | // we can record the user's action in | 
|  | // locationManagerDidChangeAuthorizationStatus:. | 
|  | BOOL systemPrompt_; | 
|  |  | 
|  | // Records whether we are prompting for a new user, so that we can record the | 
|  | // user's action to the right histogram (either | 
|  | // kGeolocationAuthorizationActionExistingUser or | 
|  | // kGeolocationAuthorizationActionNewUser). | 
|  | BOOL newUser_; | 
|  | } | 
|  |  | 
|  | // Boolean value indicating whether geolocation is enabled for Omnibox queries. | 
|  | @property(nonatomic, readonly) BOOL enabled; | 
|  |  | 
|  | // Convenience property lazily initializes |localState_|. | 
|  | @property(nonatomic, readonly) OmniboxGeolocationLocalState* localState; | 
|  |  | 
|  | // Convenience property lazily initializes |locationManager_|. | 
|  | @property(nonatomic, readonly) LocationManager* locationManager; | 
|  |  | 
|  | // Returns YES if and only if |url| and |transition| specify an Omnibox query | 
|  | // that is eligible for geolocation. | 
|  | - (BOOL)URLIsEligibleQueryURL:(const GURL&)url | 
|  | transition:(ui::PageTransition)transition; | 
|  |  | 
|  | // Returns YES if and only if |url| and |transition| specify an Omnibox query. | 
|  | // | 
|  | // Note: URLIsQueryURL:transition: is more liberal than | 
|  | // URLIsEligibleQueryURL:transition:. Use URLIsEligibleQueryURL:transition: and | 
|  | // not URLIsQueryURL:transition: to test Omnibox query URLs with respect to | 
|  | // sending location to Google. | 
|  | - (BOOL)URLIsQueryURL:(const GURL&)url | 
|  | transition:(ui::PageTransition)transition; | 
|  |  | 
|  | // Returns YES if and only if |url| specifies a page for which we will prompt | 
|  | // the user to authorize the use of geolocation for Omnibox queries. | 
|  | - (BOOL)URLIsAuthorizationPromptingURL:(const GURL&)url; | 
|  |  | 
|  | // Starts updating device location if needed. | 
|  | - (void)startUpdatingLocation; | 
|  | // Stops updating device location. | 
|  | - (void)stopUpdatingLocation; | 
|  | // If the current location is not stale, then adds the current location to the | 
|  | // current session entry for |tab| and reloads |tab|. If the current location | 
|  | // is stale, then does nothing. | 
|  | - (void)addLocationAndReloadTab:(Tab*)tab; | 
|  | // Returns YES if and only if we should show an alert that prompts the user to | 
|  | // authorize using geolocation for Omnibox queries. | 
|  | - (BOOL)shouldShowAuthorizationAlert; | 
|  | // Shows an alert that prompts the user to authorize using geolocation for | 
|  | // Omnibox queries. Sets |weakTabToReload_| from |tab|, so that we can reload | 
|  | // |tab| if the user authorizes using geolocation. | 
|  | - (void)showAuthorizationAlertForTab:(Tab*)tab; | 
|  | // Records |headerState| for the |kGeolocationHeaderSentOrNotHistogram| | 
|  | // histogram. | 
|  | - (void)recordHeaderState:(HeaderState)headerState; | 
|  | // Records |authorizationAction|. | 
|  | - (void)recordAuthorizationAction:(AuthorizationAction)authorizationAction; | 
|  |  | 
|  | @end | 
|  |  | 
|  | @implementation OmniboxGeolocationController | 
|  |  | 
|  | + (OmniboxGeolocationController*)sharedInstance { | 
|  | static OmniboxGeolocationController* instance = | 
|  | [[OmniboxGeolocationController alloc] init]; | 
|  | return instance; | 
|  | } | 
|  |  | 
|  | - (void)triggerSystemPromptForNewUser:(BOOL)newUser { | 
|  | if (self.locationManager.locationServicesEnabled && | 
|  | self.locationManager.authorizationStatus == | 
|  | kCLAuthorizationStatusNotDetermined) { | 
|  | // Set |systemPrompt_|, so that | 
|  | // locationManagerDidChangeAuthorizationStatus: will know to handle any | 
|  | // CLAuthorizationStatus changes. | 
|  | // | 
|  | // TODO(crbug.com/661996): Remove the now useless | 
|  | // kAuthorizationStateNotDeterminedSystemPrompt from | 
|  | // omnibox_geolocation_local_state.h. | 
|  | systemPrompt_ = YES; | 
|  | self.localState.authorizationState = | 
|  | geolocation::kAuthorizationStateNotDeterminedSystemPrompt; | 
|  |  | 
|  | // Turn on location updates, so that iOS will prompt the user. | 
|  | [self startUpdatingLocation]; | 
|  |  | 
|  | weakTabToReload_.reset(); | 
|  | newUser_ = newUser; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)locationBarDidBecomeFirstResponder: | 
|  | (ios::ChromeBrowserState*)browserState { | 
|  | if (self.enabled && browserState && !browserState->IsOffTheRecord()) { | 
|  | [self startUpdatingLocation]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)locationBarDidResignFirstResponder: | 
|  | (ios::ChromeBrowserState*)browserState { | 
|  | // It's always okay to stop updating location. | 
|  | [self stopUpdatingLocation]; | 
|  | } | 
|  |  | 
|  | - (void)locationBarDidSubmitURL:(const GURL&)url | 
|  | transition:(ui::PageTransition)transition | 
|  | browserState:(ios::ChromeBrowserState*)browserState { | 
|  | // Stop updating the location when the user submits a query from the Omnibox. | 
|  | // We're not interested in further updates until the next time the user puts | 
|  | // the focus on the Omnbox. | 
|  | [self stopUpdatingLocation]; | 
|  | } | 
|  |  | 
|  | - (BOOL)addLocationToNavigationItem:(web::NavigationItem*)item | 
|  | browserState:(ios::ChromeBrowserState*)browserState { | 
|  | // If this is incognito mode or is not an Omnibox query, then do nothing. | 
|  | // | 
|  | // Check the URL with URLIsQueryURL:transition: here and not | 
|  | // URLIsEligibleQueryURL:transition:, because we want to log the cases where | 
|  | // we did not send the X-Geo header due to the Google search domain not being | 
|  | // whitelisted. | 
|  | DCHECK(item); | 
|  | const GURL& url = item->GetURL(); | 
|  | if (!browserState || browserState->IsOffTheRecord() || | 
|  | ![self URLIsQueryURL:url transition:item->GetTransitionType()]) { | 
|  | return NO; | 
|  | } | 
|  |  | 
|  | if (![[OmniboxGeolocationConfig sharedInstance] URLHasEligibleDomain:url]) { | 
|  | [self recordHeaderState:kHeaderStateNotSentDomainNotWhitelisted]; | 
|  | return NO; | 
|  | } | 
|  |  | 
|  | // At this point, we should only have Omnibox query URLs that are eligible | 
|  | // for geolocation. | 
|  | DCHECK([self URLIsEligibleQueryURL:url transition:item->GetTransitionType()]); | 
|  |  | 
|  | HeaderState headerState; | 
|  | if (!self.locationManager.locationServicesEnabled) { | 
|  | headerState = kHeaderStateNotSentAuthorizationChromeDenied; | 
|  | } else { | 
|  | switch (self.localState.authorizationState) { | 
|  | case geolocation::kAuthorizationStateNotDeterminedWaiting: | 
|  | case geolocation::kAuthorizationStateNotDeterminedSystemPrompt: | 
|  | if (self.locationManager.authorizationStatus == | 
|  | kCLAuthorizationStatusNotDetermined || | 
|  | [self shouldShowAuthorizationAlert]) { | 
|  | headerState = kHeaderStateNotSentAuthorizationNotDetermined; | 
|  | } else { | 
|  | DCHECK(self.locationManager.authorizationStatus == | 
|  | kCLAuthorizationStatusAuthorizedAlways || | 
|  | self.locationManager.authorizationStatus == | 
|  | kCLAuthorizationStatusAuthorizedWhenInUse); | 
|  | headerState = kHeaderStateNotSentAuthorizationOmniboxDenied; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case geolocation::kAuthorizationStateDenied: | 
|  | switch (self.locationManager.authorizationStatus) { | 
|  | case kCLAuthorizationStatusNotDetermined: | 
|  | NOTREACHED(); | 
|  | // To keep the compiler quiet about headerState not being | 
|  | // initialized in this switch case. | 
|  | headerState = kHeaderStateNotSentAuthorizationChromeDenied; | 
|  | break; | 
|  | case kCLAuthorizationStatusRestricted: | 
|  | case kCLAuthorizationStatusDenied: | 
|  | headerState = kHeaderStateNotSentAuthorizationChromeDenied; | 
|  | break; | 
|  | case kCLAuthorizationStatusAuthorizedAlways: | 
|  | case kCLAuthorizationStatusAuthorizedWhenInUse: | 
|  | headerState = kHeaderStateNotSentAuthorizationOmniboxDenied; | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case geolocation::kAuthorizationStateAuthorized: { | 
|  | DCHECK(self.enabled); | 
|  | CLLocation* currentLocation = [self.locationManager currentLocation]; | 
|  | if (!currentLocation) { | 
|  | headerState = kHeaderStateNotSentLocationNotAvailable; | 
|  | } else if (![currentLocation cr_isFreshEnough]) { | 
|  | headerState = kHeaderStateNotSentLocationStale; | 
|  | } else { | 
|  | NSDictionary* locationHTTPHeaders = | 
|  | @{ @"X-Geo" : [currentLocation cr_xGeoString] }; | 
|  | item->AddHttpRequestHeaders(locationHTTPHeaders); | 
|  | headerState = kHeaderStateSent; | 
|  |  | 
|  | NSTimeInterval acquisitionInterval = | 
|  | currentLocation.cr_acquisitionInterval; | 
|  | base::TimeDelta acquisitionTime = base::TimeDelta::FromMilliseconds( | 
|  | acquisitionInterval * base::Time::kMillisecondsPerSecond); | 
|  | UMA_HISTOGRAM_TIMES(kOmniboxQueryGeolocationAcquisitionTimeHistogram, | 
|  | acquisitionTime); | 
|  |  | 
|  | double horizontalAccuracy = currentLocation.horizontalAccuracy; | 
|  | UMA_HISTOGRAM_COUNTS_10000( | 
|  | kOmniboxQueryGeolocationHorizontalAccuracyHistogram, | 
|  | horizontalAccuracy); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | [self recordHeaderState:headerState]; | 
|  | return headerState == kHeaderStateSent; | 
|  | } | 
|  |  | 
|  | - (void)finishPageLoadForTab:(Tab*)tab loadSuccess:(BOOL)loadSuccess { | 
|  | if (tab.isPrerenderTab || !loadSuccess || !tab.browserState || | 
|  | tab.browserState->IsOffTheRecord()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK(tab.webState->GetNavigationManager()); | 
|  | web::NavigationItem* item = | 
|  | tab.webState->GetNavigationManager()->GetVisibleItem(); | 
|  | if (![self URLIsAuthorizationPromptingURL:item->GetURL()] || | 
|  | !self.locationManager.locationServicesEnabled) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | switch (self.locationManager.authorizationStatus) { | 
|  | case kCLAuthorizationStatusNotDetermined: | 
|  | // Prompt the user with the iOS system location authorization alert. | 
|  | // | 
|  | // Set |systemPrompt_|, so that | 
|  | // locationManagerDidChangeAuthorizationStatus: will know that any | 
|  | // CLAuthorizationStatus changes are coming from this specific prompt. | 
|  | systemPrompt_ = YES; | 
|  | self.localState.authorizationState = | 
|  | geolocation::kAuthorizationStateNotDeterminedSystemPrompt; | 
|  | [self startUpdatingLocation]; | 
|  |  | 
|  | // Save this tab in case we're able to transition to | 
|  | // kAuthorizationStateAuthorized. | 
|  | weakTabToReload_.reset(tab); | 
|  | break; | 
|  |  | 
|  | case kCLAuthorizationStatusRestricted: | 
|  | case kCLAuthorizationStatusDenied: | 
|  | break; | 
|  |  | 
|  | case kCLAuthorizationStatusAuthorizedAlways: | 
|  | case kCLAuthorizationStatusAuthorizedWhenInUse: | 
|  | // We might be in state kAuthorizationStateNotDeterminedSystemPrompt here | 
|  | // if we presented the iOS system location alert when | 
|  | // [CLLocationManager authorizationStatus] was | 
|  | // kCLAuthorizationStatusNotDetermined but the user managed to authorize | 
|  | // the app through some other flow; this might happen if the user | 
|  | // backgrounded the app or the app crashed. If so, then reset the state. | 
|  | if (self.localState.authorizationState == | 
|  | geolocation::kAuthorizationStateNotDeterminedSystemPrompt) { | 
|  | self.localState.authorizationState = | 
|  | geolocation::kAuthorizationStateNotDeterminedWaiting; | 
|  | } | 
|  | // If the user has authorized the app to use location but not yet | 
|  | // explicitly authorized or denied using geolocation for Omnibox queries, | 
|  | // then present an alert. | 
|  | if (self.localState.authorizationState == | 
|  | geolocation::kAuthorizationStateNotDeterminedWaiting && | 
|  | [self shouldShowAuthorizationAlert]) { | 
|  | [self showAuthorizationAlertForTab:tab]; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | #pragma mark - Private | 
|  |  | 
|  | - (BOOL)enabled { | 
|  | return self.locationManager.locationServicesEnabled && | 
|  | self.localState.authorizationState == | 
|  | geolocation::kAuthorizationStateAuthorized; | 
|  | } | 
|  |  | 
|  | - (OmniboxGeolocationLocalState*)localState { | 
|  | if (!localState_) { | 
|  | localState_.reset([[OmniboxGeolocationLocalState alloc] | 
|  | initWithLocationManager:self.locationManager]); | 
|  | } | 
|  | return localState_; | 
|  | } | 
|  |  | 
|  | - (LocationManager*)locationManager { | 
|  | if (!locationManager_) { | 
|  | locationManager_.reset([[LocationManager alloc] init]); | 
|  | [locationManager_ setDelegate:self]; | 
|  | } | 
|  | return locationManager_; | 
|  | } | 
|  |  | 
|  | - (BOOL)URLIsEligibleQueryURL:(const GURL&)url | 
|  | transition:(ui::PageTransition)transition { | 
|  | return [self URLIsQueryURL:url transition:transition] && | 
|  | [[OmniboxGeolocationConfig sharedInstance] URLHasEligibleDomain:url]; | 
|  | } | 
|  |  | 
|  | - (BOOL)URLIsQueryURL:(const GURL&)url | 
|  | transition:(ui::PageTransition)transition { | 
|  | if (google_util::IsGoogleSearchUrl(url) && | 
|  | (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0) { | 
|  | ui::PageTransition coreTransition = static_cast<ui::PageTransition>( | 
|  | transition & ui::PAGE_TRANSITION_CORE_MASK); | 
|  | if (PageTransitionCoreTypeIs(coreTransition, | 
|  | ui::PAGE_TRANSITION_GENERATED) || | 
|  | PageTransitionCoreTypeIs(coreTransition, ui::PAGE_TRANSITION_RELOAD)) { | 
|  | return YES; | 
|  | } | 
|  | } | 
|  | return NO; | 
|  | } | 
|  |  | 
|  | - (BOOL)URLIsAuthorizationPromptingURL:(const GURL&)url { | 
|  | // Per PRD: "Show a modal dialog upon reaching google.com or a search results | 
|  | // page..." However, we only want to do this for domains where we will send | 
|  | // location. | 
|  | return (google_util::IsGoogleHomePageUrl(url) || | 
|  | google_util::IsGoogleSearchUrl(url)) && | 
|  | [[OmniboxGeolocationConfig sharedInstance] URLHasEligibleDomain:url]; | 
|  | } | 
|  |  | 
|  | - (void)startUpdatingLocation { | 
|  | // Note that GeolocationUpdater will stop itself automatically after 5 | 
|  | // seconds. | 
|  | [self.locationManager startUpdatingLocation]; | 
|  | } | 
|  |  | 
|  | - (void)stopUpdatingLocation { | 
|  | // Note that we don't need to initialize |locationManager_| here. If it's | 
|  | // nil, then it's not running. | 
|  | [locationManager_ stopUpdatingLocation]; | 
|  | } | 
|  |  | 
|  | - (void)addLocationAndReloadTab:(Tab*)tab { | 
|  | if (self.enabled && tab.webState) { | 
|  | // Make sure that GeolocationUpdater is running the first time we request | 
|  | // the current location. | 
|  | // | 
|  | // If GeolocationUpdater is not running, then it returns nil for the | 
|  | // current location. That's normally okay, because we cache the most recent | 
|  | // location in LocationManager. However, we arrive here when the user first | 
|  | // authorizes us to use location, so we may not have ever started | 
|  | // GeolocationUpdater. | 
|  | [self startUpdatingLocation]; | 
|  |  | 
|  | web::NavigationManager* navigationManager = | 
|  | tab.webState->GetNavigationManager(); | 
|  | web::NavigationItem* item = navigationManager->GetVisibleItem(); | 
|  | if ([self addLocationToNavigationItem:item browserState:tab.browserState]) { | 
|  | navigationManager->Reload(web::ReloadType::NORMAL, | 
|  | false /* check_for_repost */); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | - (BOOL)shouldShowAuthorizationAlert { | 
|  | base::Version previousVersion(self.localState.lastAuthorizationAlertVersion); | 
|  | if (!previousVersion.IsValid()) | 
|  | return YES; | 
|  |  | 
|  | base::Version currentVersion(version_info::GetVersionNumber()); | 
|  | DCHECK(currentVersion.IsValid()); | 
|  | return currentVersion.components()[0] != previousVersion.components()[0]; | 
|  | } | 
|  |  | 
|  | - (void)showAuthorizationAlertForTab:(Tab*)tab { | 
|  | // Save this tab in case we're able to transition to | 
|  | // kAuthorizationStateAuthorized. | 
|  | weakTabToReload_.reset(tab); | 
|  |  | 
|  | authorizationAlert_.reset( | 
|  | [[OmniboxGeolocationAuthorizationAlert alloc] initWithDelegate:self]); | 
|  | [authorizationAlert_ showAuthorizationAlert]; | 
|  |  | 
|  | self.localState.lastAuthorizationAlertVersion = | 
|  | version_info::GetVersionNumber(); | 
|  | } | 
|  |  | 
|  | - (void)recordHeaderState:(HeaderState)headerState { | 
|  | UMA_HISTOGRAM_ENUMERATION(kGeolocationHeaderSentOrNotHistogram, headerState, | 
|  | kHeaderStateCount); | 
|  | } | 
|  |  | 
|  | - (void)recordAuthorizationAction:(AuthorizationAction)authorizationAction { | 
|  | if (newUser_) { | 
|  | newUser_ = NO; | 
|  |  | 
|  | UMA_HISTOGRAM_ENUMERATION(kGeolocationAuthorizationActionNewUser, | 
|  | authorizationAction, kAuthorizationActionCount); | 
|  | } else { | 
|  | UMA_HISTOGRAM_ENUMERATION(kGeolocationAuthorizationActionExistingUser, | 
|  | authorizationAction, kAuthorizationActionCount); | 
|  | } | 
|  | } | 
|  |  | 
|  | #pragma mark - LocationManagerDelegate | 
|  |  | 
|  | - (void)locationManagerDidChangeAuthorizationStatus: | 
|  | (LocationManager*)locationManager { | 
|  | if (systemPrompt_) { | 
|  | switch (self.locationManager.authorizationStatus) { | 
|  | case kCLAuthorizationStatusNotDetermined: | 
|  | // We may get a spurious notification about a transition to | 
|  | // |kCLAuthorizationStatusNotDetermined| when we first start location | 
|  | // services. Ignore it and don't reset |systemPrompt_| until we get a | 
|  | // real change. | 
|  | break; | 
|  |  | 
|  | case kCLAuthorizationStatusRestricted: | 
|  | case kCLAuthorizationStatusDenied: | 
|  | self.localState.authorizationState = | 
|  | geolocation::kAuthorizationStateDenied; | 
|  | systemPrompt_ = NO; | 
|  |  | 
|  | [self recordAuthorizationAction:kAuthorizationActionPermanentlyDenied]; | 
|  | break; | 
|  |  | 
|  | case kCLAuthorizationStatusAuthorizedAlways: | 
|  | case kCLAuthorizationStatusAuthorizedWhenInUse: | 
|  | self.localState.authorizationState = | 
|  | geolocation::kAuthorizationStateAuthorized; | 
|  | systemPrompt_ = NO; | 
|  |  | 
|  | base::scoped_nsobject<Tab> tab([weakTabToReload_ retain]); | 
|  | [self addLocationAndReloadTab:tab]; | 
|  | weakTabToReload_.reset(); | 
|  |  | 
|  | [self recordAuthorizationAction:kAuthorizationActionAuthorized]; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #pragma mark - OmniboxGeolocationAuthorizationAlertDelegate | 
|  |  | 
|  | - (void)authorizationAlertDidAuthorize: | 
|  | (OmniboxGeolocationAuthorizationAlert*)authorizationAlert { | 
|  | self.localState.authorizationState = | 
|  | geolocation::kAuthorizationStateAuthorized; | 
|  |  | 
|  | base::scoped_nsobject<Tab> tab([weakTabToReload_ retain]); | 
|  | [self addLocationAndReloadTab:tab]; | 
|  |  | 
|  | // Just resetting |authorizationAlert_| leads to a user-after-free crash | 
|  | // presumably due to a UIKit bug. Making authorizationAlert_ autorelease | 
|  | // will keep it alive long enough to avoid the crash. See crbug.com/381235 | 
|  | authorizationAlert_.autorelease(); | 
|  | weakTabToReload_.reset(); | 
|  |  | 
|  | [self recordAuthorizationAction:kAuthorizationActionAuthorized]; | 
|  | } | 
|  |  | 
|  | - (void)authorizationAlertDidCancel: | 
|  | (OmniboxGeolocationAuthorizationAlert*)authorizationAlert { | 
|  | // Leave authorization state as undetermined (not kAuthorizationStateDenied). | 
|  | // We won't use location, but we'll still be able to prompt at the next | 
|  | // application update. | 
|  |  | 
|  | // Just resetting |authorizationAlert_| leads to a user-after-free crash | 
|  | // presumably due to a UIKit bug. Making authorizationAlert_ autorelease | 
|  | // will keep it alive long enough to avoid the crash. See crbug.com/381235 | 
|  | authorizationAlert_.autorelease(); | 
|  | weakTabToReload_.reset(); | 
|  |  | 
|  | [self recordAuthorizationAction:kAuthorizationActionDenied]; | 
|  | } | 
|  |  | 
|  | #pragma mark - OmniboxGeolocationController+Testing | 
|  |  | 
|  | - (void)setLocalState:(OmniboxGeolocationLocalState*)localState { | 
|  | localState_.reset([localState retain]); | 
|  | } | 
|  |  | 
|  | - (void)setLocationManager:(LocationManager*)locationManager { | 
|  | locationManager_.reset([locationManager retain]); | 
|  | } | 
|  |  | 
|  | @end |