OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h" |
| 6 |
| 7 #import <CoreLocation/CoreLocation.h> |
| 8 #import <UIKit/UIKit.h> |
| 9 |
| 10 #include <string> |
| 11 |
| 12 #import "base/ios/weak_nsobject.h" |
| 13 #include "base/logging.h" |
| 14 #include "base/mac/scoped_nsobject.h" |
| 15 #include "base/metrics/histogram.h" |
| 16 #include "base/version.h" |
| 17 #include "components/google/core/browser/google_util.h" |
| 18 #include "components/version_info/version_info.h" |
| 19 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 20 #import "ios/chrome/browser/geolocation/CLLocation+OmniboxGeolocation.h" |
| 21 #import "ios/chrome/browser/geolocation/CLLocation+XGeoHeader.h" |
| 22 #import "ios/chrome/browser/geolocation/location_manager.h" |
| 23 #import "ios/chrome/browser/geolocation/omnibox_geolocation_authorization_alert.
h" |
| 24 #import "ios/chrome/browser/geolocation/omnibox_geolocation_config.h" |
| 25 #import "ios/chrome/browser/geolocation/omnibox_geolocation_controller+Testing.h
" |
| 26 #import "ios/chrome/browser/geolocation/omnibox_geolocation_local_state.h" |
| 27 #import "ios/chrome/browser/tabs/tab.h" |
| 28 #include "ios/web/public/navigation_item.h" |
| 29 #import "ios/web/public/navigation_manager.h" |
| 30 #include "url/gurl.h" |
| 31 |
| 32 namespace { |
| 33 |
| 34 // Values for the histogram that records whether we sent the X-Geo header for |
| 35 // an Omnibox query or why we did not do so. These match the definition of |
| 36 // GeolocationHeaderSentOrNot in Chromium |
| 37 // src-internal/tools/histograms/histograms.xml. |
| 38 typedef enum { |
| 39 // The user disabled location for Google.com (not used by Chrome iOS). |
| 40 kHeaderStateNotSentAuthorizationGoogleDenied = 0, |
| 41 // The user has not yet determined Chrome's access to the current device |
| 42 // location or Chrome's use of geolocation for Omnibox queries. |
| 43 kHeaderStateNotSentAuthorizationNotDetermined, |
| 44 // The current device location is not available. |
| 45 kHeaderStateNotSentLocationNotAvailable, |
| 46 // The current device location is stale. |
| 47 kHeaderStateNotSentLocationStale, |
| 48 // The X-Geo header was sent. |
| 49 kHeaderStateSent, |
| 50 // The user denied Chrome from accessing the current device location. |
| 51 kHeaderStateNotSentAuthorizationChromeDenied, |
| 52 // The user denied Chrome from using geolocation for Omnibox queries. |
| 53 kHeaderStateNotSentAuthorizationOmniboxDenied, |
| 54 // The user's Google search domain is not whitelisted. |
| 55 kHeaderStateNotSentDomainNotWhitelisted, |
| 56 // The number of possible of HeaderState values to report. |
| 57 kHeaderStateCount, |
| 58 } HeaderState; |
| 59 |
| 60 // Values for the histograms that record the user's action when prompted to |
| 61 // authorize the use of location by Chrome. These match the definition of |
| 62 // GeolocationAuthorizationAction in Chromium |
| 63 // src-internal/tools/histograms/histograms.xml. |
| 64 typedef enum { |
| 65 // The user authorized use of location. |
| 66 kAuthorizationActionAuthorized = 0, |
| 67 // The user permanently denied use of location (Don't Allow). |
| 68 kAuthorizationActionPermanentlyDenied, |
| 69 // The user denied use of location at this prompt (Not Now). |
| 70 kAuthorizationActionDenied, |
| 71 // The number of possible AuthorizationAction values to report. |
| 72 kAuthorizationActionCount, |
| 73 } AuthorizationAction; |
| 74 |
| 75 // Name of the histogram recording HeaderState. |
| 76 const char* const kGeolocationHeaderSentOrNotHistogram = |
| 77 "Geolocation.HeaderSentOrNot"; |
| 78 |
| 79 // Name of the histogram recording location acquisition time. |
| 80 const char* const kOmniboxQueryGeolocationAcquisitionTimeHistogram = |
| 81 "Omnibox.QueryGeolocationAcquisitionTime"; |
| 82 |
| 83 // Name of the histogram recording estimated location accuracy. |
| 84 const char* const kOmniboxQueryGeolocationHorizontalAccuracyHistogram = |
| 85 "Omnibox.QueryGeolocationHorizontalAccuracy"; |
| 86 |
| 87 // Name of the histogram recording AuthorizationAction for an existing user. |
| 88 const char* const kGeolocationAuthorizationActionExistingUser = |
| 89 "Geolocation.AuthorizationActionExistingUser"; |
| 90 |
| 91 // Name of the histogram recording AuthorizationAction for a new user. |
| 92 const char* const kGeolocationAuthorizationActionNewUser = |
| 93 "Geolocation.AuthorizationActionNewUser"; |
| 94 |
| 95 } // anonymous namespace |
| 96 |
| 97 @interface OmniboxGeolocationController ()< |
| 98 LocationManagerDelegate, |
| 99 OmniboxGeolocationAuthorizationAlertDelegate> { |
| 100 base::scoped_nsobject<OmniboxGeolocationLocalState> localState_; |
| 101 base::scoped_nsobject<LocationManager> locationManager_; |
| 102 base::scoped_nsobject<OmniboxGeolocationAuthorizationAlert> |
| 103 authorizationAlert_; |
| 104 base::WeakNSObject<Tab> weakTabToReload_; |
| 105 |
| 106 // Records whether we have deliberately presented the system prompt, so that |
| 107 // we can record the user's action in |
| 108 // locationManagerDidChangeAuthorizationStatus:. |
| 109 BOOL systemPrompt_; |
| 110 |
| 111 // Records whether we are prompting for a new user, so that we can record the |
| 112 // user's action to the right histogram (either |
| 113 // kGeolocationAuthorizationActionExistingUser or |
| 114 // kGeolocationAuthorizationActionNewUser). |
| 115 BOOL newUser_; |
| 116 } |
| 117 |
| 118 // Boolean value indicating whether geolocation is enabled for Omnibox queries. |
| 119 @property(nonatomic, readonly) BOOL enabled; |
| 120 |
| 121 // Convenience property lazily initializes |localState_|. |
| 122 @property(nonatomic, readonly) OmniboxGeolocationLocalState* localState; |
| 123 |
| 124 // Convenience property lazily initializes |locationManager_|. |
| 125 @property(nonatomic, readonly) LocationManager* locationManager; |
| 126 |
| 127 // Returns YES if and only if |url| and |transition| specify an Omnibox query |
| 128 // that is eligible for geolocation. |
| 129 - (BOOL)URLIsEligibleQueryURL:(const GURL&)url |
| 130 transition:(ui::PageTransition)transition; |
| 131 |
| 132 // Returns YES if and only if |url| and |transition| specify an Omnibox query. |
| 133 // |
| 134 // Note: URLIsQueryURL:transition: is more liberal than |
| 135 // URLIsEligibleQueryURL:transition:. Use URLIsEligibleQueryURL:transition: and |
| 136 // not URLIsQueryURL:transition: to test Omnibox query URLs with respect to |
| 137 // sending location to Google. |
| 138 - (BOOL)URLIsQueryURL:(const GURL&)url |
| 139 transition:(ui::PageTransition)transition; |
| 140 |
| 141 // Returns YES if and only if |url| specifies a page for which we will prompt |
| 142 // the user to authorize the use of geolocation for Omnibox queries. |
| 143 - (BOOL)URLIsAuthorizationPromptingURL:(const GURL&)url; |
| 144 |
| 145 // Starts updating device location if needed. |
| 146 - (void)startUpdatingLocation; |
| 147 // Stops updating device location. |
| 148 - (void)stopUpdatingLocation; |
| 149 // If the current location is not stale, then adds the current location to the |
| 150 // current session entry for |tab| and reloads |tab|. If the current location |
| 151 // is stale, then does nothing. |
| 152 - (void)addLocationAndReloadTab:(Tab*)tab; |
| 153 // Returns YES if and only if we should show an alert that prompts the user to |
| 154 // authorize using geolocation for Omnibox queries. |
| 155 - (BOOL)shouldShowAuthorizationAlert; |
| 156 // Shows an alert that prompts the user to authorize using geolocation for |
| 157 // Omnibox queries. Sets |weakTabToReload_| from |tab|, so that we can reload |
| 158 // |tab| if the user authorizes using geolocation. |
| 159 - (void)showAuthorizationAlertForTab:(Tab*)tab; |
| 160 // Records |headerState| for the |kGeolocationHeaderSentOrNotHistogram| |
| 161 // histogram. |
| 162 - (void)recordHeaderState:(HeaderState)headerState; |
| 163 // Records |authorizationAction|. |
| 164 - (void)recordAuthorizationAction:(AuthorizationAction)authorizationAction; |
| 165 |
| 166 @end |
| 167 |
| 168 @implementation OmniboxGeolocationController |
| 169 |
| 170 + (OmniboxGeolocationController*)sharedInstance { |
| 171 static OmniboxGeolocationController* instance = |
| 172 [[OmniboxGeolocationController alloc] init]; |
| 173 return instance; |
| 174 } |
| 175 |
| 176 - (void)triggerSystemPromptForNewUser:(BOOL)newUser { |
| 177 if (self.locationManager.locationServicesEnabled && |
| 178 self.locationManager.authorizationStatus == |
| 179 kCLAuthorizationStatusNotDetermined) { |
| 180 // Set |systemPrompt_|, so that |
| 181 // locationManagerDidChangeAuthorizationStatus: will know to handle any |
| 182 // CLAuthorizationStatus changes. |
| 183 // |
| 184 // TODO(crbug.com/661996): Remove the now useless |
| 185 // kAuthorizationStateNotDeterminedSystemPrompt from |
| 186 // omnibox_geolocation_local_state.h. |
| 187 systemPrompt_ = YES; |
| 188 self.localState.authorizationState = |
| 189 geolocation::kAuthorizationStateNotDeterminedSystemPrompt; |
| 190 |
| 191 // Turn on location updates, so that iOS will prompt the user. |
| 192 [self startUpdatingLocation]; |
| 193 |
| 194 weakTabToReload_.reset(); |
| 195 newUser_ = newUser; |
| 196 } |
| 197 } |
| 198 |
| 199 - (void)locationBarDidBecomeFirstResponder: |
| 200 (ios::ChromeBrowserState*)browserState { |
| 201 if (self.enabled && browserState && !browserState->IsOffTheRecord()) { |
| 202 [self startUpdatingLocation]; |
| 203 } |
| 204 } |
| 205 |
| 206 - (void)locationBarDidResignFirstResponder: |
| 207 (ios::ChromeBrowserState*)browserState { |
| 208 // It's always okay to stop updating location. |
| 209 [self stopUpdatingLocation]; |
| 210 } |
| 211 |
| 212 - (void)locationBarDidSubmitURL:(const GURL&)url |
| 213 transition:(ui::PageTransition)transition |
| 214 browserState:(ios::ChromeBrowserState*)browserState { |
| 215 // Stop updating the location when the user submits a query from the Omnibox. |
| 216 // We're not interested in further updates until the next time the user puts |
| 217 // the focus on the Omnbox. |
| 218 [self stopUpdatingLocation]; |
| 219 } |
| 220 |
| 221 - (BOOL)addLocationToNavigationItem:(web::NavigationItem*)item |
| 222 browserState:(ios::ChromeBrowserState*)browserState { |
| 223 // If this is incognito mode or is not an Omnibox query, then do nothing. |
| 224 // |
| 225 // Check the URL with URLIsQueryURL:transition: here and not |
| 226 // URLIsEligibleQueryURL:transition:, because we want to log the cases where |
| 227 // we did not send the X-Geo header due to the Google search domain not being |
| 228 // whitelisted. |
| 229 DCHECK(item); |
| 230 const GURL& url = item->GetURL(); |
| 231 if (!browserState || browserState->IsOffTheRecord() || |
| 232 ![self URLIsQueryURL:url transition:item->GetTransitionType()]) { |
| 233 return NO; |
| 234 } |
| 235 |
| 236 if (![[OmniboxGeolocationConfig sharedInstance] URLHasEligibleDomain:url]) { |
| 237 [self recordHeaderState:kHeaderStateNotSentDomainNotWhitelisted]; |
| 238 return NO; |
| 239 } |
| 240 |
| 241 // At this point, we should only have Omnibox query URLs that are eligible |
| 242 // for geolocation. |
| 243 DCHECK([self URLIsEligibleQueryURL:url transition:item->GetTransitionType()]); |
| 244 |
| 245 HeaderState headerState; |
| 246 if (!self.locationManager.locationServicesEnabled) { |
| 247 headerState = kHeaderStateNotSentAuthorizationChromeDenied; |
| 248 } else { |
| 249 switch (self.localState.authorizationState) { |
| 250 case geolocation::kAuthorizationStateNotDeterminedWaiting: |
| 251 case geolocation::kAuthorizationStateNotDeterminedSystemPrompt: |
| 252 if (self.locationManager.authorizationStatus == |
| 253 kCLAuthorizationStatusNotDetermined || |
| 254 [self shouldShowAuthorizationAlert]) { |
| 255 headerState = kHeaderStateNotSentAuthorizationNotDetermined; |
| 256 } else { |
| 257 DCHECK(self.locationManager.authorizationStatus == |
| 258 kCLAuthorizationStatusAuthorizedAlways || |
| 259 self.locationManager.authorizationStatus == |
| 260 kCLAuthorizationStatusAuthorizedWhenInUse); |
| 261 headerState = kHeaderStateNotSentAuthorizationOmniboxDenied; |
| 262 } |
| 263 break; |
| 264 |
| 265 case geolocation::kAuthorizationStateDenied: |
| 266 switch (self.locationManager.authorizationStatus) { |
| 267 case kCLAuthorizationStatusNotDetermined: |
| 268 NOTREACHED(); |
| 269 // To keep the compiler quiet about headerState not being |
| 270 // initialized in this switch case. |
| 271 headerState = kHeaderStateNotSentAuthorizationChromeDenied; |
| 272 break; |
| 273 case kCLAuthorizationStatusRestricted: |
| 274 case kCLAuthorizationStatusDenied: |
| 275 headerState = kHeaderStateNotSentAuthorizationChromeDenied; |
| 276 break; |
| 277 case kCLAuthorizationStatusAuthorizedAlways: |
| 278 case kCLAuthorizationStatusAuthorizedWhenInUse: |
| 279 headerState = kHeaderStateNotSentAuthorizationOmniboxDenied; |
| 280 break; |
| 281 } |
| 282 break; |
| 283 |
| 284 case geolocation::kAuthorizationStateAuthorized: { |
| 285 DCHECK(self.enabled); |
| 286 CLLocation* currentLocation = [self.locationManager currentLocation]; |
| 287 if (!currentLocation) { |
| 288 headerState = kHeaderStateNotSentLocationNotAvailable; |
| 289 } else if (![currentLocation cr_isFreshEnough]) { |
| 290 headerState = kHeaderStateNotSentLocationStale; |
| 291 } else { |
| 292 NSDictionary* locationHTTPHeaders = |
| 293 @{ @"X-Geo" : [currentLocation cr_xGeoString] }; |
| 294 item->AddHttpRequestHeaders(locationHTTPHeaders); |
| 295 headerState = kHeaderStateSent; |
| 296 |
| 297 NSTimeInterval acquisitionInterval = |
| 298 currentLocation.cr_acquisitionInterval; |
| 299 base::TimeDelta acquisitionTime = base::TimeDelta::FromMilliseconds( |
| 300 acquisitionInterval * base::Time::kMillisecondsPerSecond); |
| 301 UMA_HISTOGRAM_TIMES(kOmniboxQueryGeolocationAcquisitionTimeHistogram, |
| 302 acquisitionTime); |
| 303 |
| 304 double horizontalAccuracy = currentLocation.horizontalAccuracy; |
| 305 UMA_HISTOGRAM_COUNTS_10000( |
| 306 kOmniboxQueryGeolocationHorizontalAccuracyHistogram, |
| 307 horizontalAccuracy); |
| 308 } |
| 309 break; |
| 310 } |
| 311 } |
| 312 } |
| 313 |
| 314 [self recordHeaderState:headerState]; |
| 315 return headerState == kHeaderStateSent; |
| 316 } |
| 317 |
| 318 - (void)finishPageLoadForTab:(Tab*)tab loadSuccess:(BOOL)loadSuccess { |
| 319 if (tab.isPrerenderTab || !loadSuccess || !tab.browserState || |
| 320 tab.browserState->IsOffTheRecord()) { |
| 321 return; |
| 322 } |
| 323 |
| 324 DCHECK(tab.webState->GetNavigationManager()); |
| 325 web::NavigationItem* item = |
| 326 tab.webState->GetNavigationManager()->GetVisibleItem(); |
| 327 if (![self URLIsAuthorizationPromptingURL:item->GetURL()] || |
| 328 !self.locationManager.locationServicesEnabled) { |
| 329 return; |
| 330 } |
| 331 |
| 332 switch (self.locationManager.authorizationStatus) { |
| 333 case kCLAuthorizationStatusNotDetermined: |
| 334 // Prompt the user with the iOS system location authorization alert. |
| 335 // |
| 336 // Set |systemPrompt_|, so that |
| 337 // locationManagerDidChangeAuthorizationStatus: will know that any |
| 338 // CLAuthorizationStatus changes are coming from this specific prompt. |
| 339 systemPrompt_ = YES; |
| 340 self.localState.authorizationState = |
| 341 geolocation::kAuthorizationStateNotDeterminedSystemPrompt; |
| 342 [self startUpdatingLocation]; |
| 343 |
| 344 // Save this tab in case we're able to transition to |
| 345 // kAuthorizationStateAuthorized. |
| 346 weakTabToReload_.reset(tab); |
| 347 break; |
| 348 |
| 349 case kCLAuthorizationStatusRestricted: |
| 350 case kCLAuthorizationStatusDenied: |
| 351 break; |
| 352 |
| 353 case kCLAuthorizationStatusAuthorizedAlways: |
| 354 case kCLAuthorizationStatusAuthorizedWhenInUse: |
| 355 // We might be in state kAuthorizationStateNotDeterminedSystemPrompt here |
| 356 // if we presented the iOS system location alert when |
| 357 // [CLLocationManager authorizationStatus] was |
| 358 // kCLAuthorizationStatusNotDetermined but the user managed to authorize |
| 359 // the app through some other flow; this might happen if the user |
| 360 // backgrounded the app or the app crashed. If so, then reset the state. |
| 361 if (self.localState.authorizationState == |
| 362 geolocation::kAuthorizationStateNotDeterminedSystemPrompt) { |
| 363 self.localState.authorizationState = |
| 364 geolocation::kAuthorizationStateNotDeterminedWaiting; |
| 365 } |
| 366 // If the user has authorized the app to use location but not yet |
| 367 // explicitly authorized or denied using geolocation for Omnibox queries, |
| 368 // then present an alert. |
| 369 if (self.localState.authorizationState == |
| 370 geolocation::kAuthorizationStateNotDeterminedWaiting && |
| 371 [self shouldShowAuthorizationAlert]) { |
| 372 [self showAuthorizationAlertForTab:tab]; |
| 373 } |
| 374 break; |
| 375 } |
| 376 } |
| 377 |
| 378 #pragma mark - Private |
| 379 |
| 380 - (BOOL)enabled { |
| 381 return self.locationManager.locationServicesEnabled && |
| 382 self.localState.authorizationState == |
| 383 geolocation::kAuthorizationStateAuthorized; |
| 384 } |
| 385 |
| 386 - (OmniboxGeolocationLocalState*)localState { |
| 387 if (!localState_) { |
| 388 localState_.reset([[OmniboxGeolocationLocalState alloc] |
| 389 initWithLocationManager:self.locationManager]); |
| 390 } |
| 391 return localState_; |
| 392 } |
| 393 |
| 394 - (LocationManager*)locationManager { |
| 395 if (!locationManager_) { |
| 396 locationManager_.reset([[LocationManager alloc] init]); |
| 397 [locationManager_ setDelegate:self]; |
| 398 } |
| 399 return locationManager_; |
| 400 } |
| 401 |
| 402 - (BOOL)URLIsEligibleQueryURL:(const GURL&)url |
| 403 transition:(ui::PageTransition)transition { |
| 404 return [self URLIsQueryURL:url transition:transition] && |
| 405 [[OmniboxGeolocationConfig sharedInstance] URLHasEligibleDomain:url]; |
| 406 } |
| 407 |
| 408 - (BOOL)URLIsQueryURL:(const GURL&)url |
| 409 transition:(ui::PageTransition)transition { |
| 410 if (google_util::IsGoogleSearchUrl(url) && |
| 411 (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0) { |
| 412 ui::PageTransition coreTransition = static_cast<ui::PageTransition>( |
| 413 transition & ui::PAGE_TRANSITION_CORE_MASK); |
| 414 if (PageTransitionCoreTypeIs(coreTransition, |
| 415 ui::PAGE_TRANSITION_GENERATED) || |
| 416 PageTransitionCoreTypeIs(coreTransition, ui::PAGE_TRANSITION_RELOAD)) { |
| 417 return YES; |
| 418 } |
| 419 } |
| 420 return NO; |
| 421 } |
| 422 |
| 423 - (BOOL)URLIsAuthorizationPromptingURL:(const GURL&)url { |
| 424 // Per PRD: "Show a modal dialog upon reaching google.com or a search results |
| 425 // page..." However, we only want to do this for domains where we will send |
| 426 // location. |
| 427 return (google_util::IsGoogleHomePageUrl(url) || |
| 428 google_util::IsGoogleSearchUrl(url)) && |
| 429 [[OmniboxGeolocationConfig sharedInstance] URLHasEligibleDomain:url]; |
| 430 } |
| 431 |
| 432 - (void)startUpdatingLocation { |
| 433 // Note that GeolocationUpdater will stop itself automatically after 5 |
| 434 // seconds. |
| 435 [self.locationManager startUpdatingLocation]; |
| 436 } |
| 437 |
| 438 - (void)stopUpdatingLocation { |
| 439 // Note that we don't need to initialize |locationManager_| here. If it's |
| 440 // nil, then it's not running. |
| 441 [locationManager_ stopUpdatingLocation]; |
| 442 } |
| 443 |
| 444 - (void)addLocationAndReloadTab:(Tab*)tab { |
| 445 if (self.enabled && [tab navigationManager]) { |
| 446 // Make sure that GeolocationUpdater is running the first time we request |
| 447 // the current location. |
| 448 // |
| 449 // If GeolocationUpdater is not running, then it returns nil for the |
| 450 // current location. That's normally okay, because we cache the most recent |
| 451 // location in LocationManager. However, we arrive here when the user first |
| 452 // authorizes us to use location, so we may not have ever started |
| 453 // GeolocationUpdater. |
| 454 [self startUpdatingLocation]; |
| 455 |
| 456 web::NavigationItem* item = |
| 457 tab.webState->GetNavigationManager()->GetVisibleItem(); |
| 458 if ([self addLocationToNavigationItem:item browserState:tab.browserState]) { |
| 459 [tab reload]; |
| 460 } |
| 461 } |
| 462 } |
| 463 |
| 464 - (BOOL)shouldShowAuthorizationAlert { |
| 465 base::Version previousVersion(self.localState.lastAuthorizationAlertVersion); |
| 466 if (!previousVersion.IsValid()) |
| 467 return YES; |
| 468 |
| 469 base::Version currentVersion(version_info::GetVersionNumber()); |
| 470 DCHECK(currentVersion.IsValid()); |
| 471 return currentVersion.components()[0] != previousVersion.components()[0]; |
| 472 } |
| 473 |
| 474 - (void)showAuthorizationAlertForTab:(Tab*)tab { |
| 475 // Save this tab in case we're able to transition to |
| 476 // kAuthorizationStateAuthorized. |
| 477 weakTabToReload_.reset(tab); |
| 478 |
| 479 authorizationAlert_.reset( |
| 480 [[OmniboxGeolocationAuthorizationAlert alloc] initWithDelegate:self]); |
| 481 [authorizationAlert_ showAuthorizationAlert]; |
| 482 |
| 483 self.localState.lastAuthorizationAlertVersion = |
| 484 version_info::GetVersionNumber(); |
| 485 } |
| 486 |
| 487 - (void)recordHeaderState:(HeaderState)headerState { |
| 488 UMA_HISTOGRAM_ENUMERATION(kGeolocationHeaderSentOrNotHistogram, headerState, |
| 489 kHeaderStateCount); |
| 490 } |
| 491 |
| 492 - (void)recordAuthorizationAction:(AuthorizationAction)authorizationAction { |
| 493 if (newUser_) { |
| 494 newUser_ = NO; |
| 495 |
| 496 UMA_HISTOGRAM_ENUMERATION(kGeolocationAuthorizationActionNewUser, |
| 497 authorizationAction, kAuthorizationActionCount); |
| 498 } else { |
| 499 UMA_HISTOGRAM_ENUMERATION(kGeolocationAuthorizationActionExistingUser, |
| 500 authorizationAction, kAuthorizationActionCount); |
| 501 } |
| 502 } |
| 503 |
| 504 #pragma mark - LocationManagerDelegate |
| 505 |
| 506 - (void)locationManagerDidChangeAuthorizationStatus: |
| 507 (LocationManager*)locationManager { |
| 508 if (systemPrompt_) { |
| 509 switch (self.locationManager.authorizationStatus) { |
| 510 case kCLAuthorizationStatusNotDetermined: |
| 511 // We may get a spurious notification about a transition to |
| 512 // |kCLAuthorizationStatusNotDetermined| when we first start location |
| 513 // services. Ignore it and don't reset |systemPrompt_| until we get a |
| 514 // real change. |
| 515 break; |
| 516 |
| 517 case kCLAuthorizationStatusRestricted: |
| 518 case kCLAuthorizationStatusDenied: |
| 519 self.localState.authorizationState = |
| 520 geolocation::kAuthorizationStateDenied; |
| 521 systemPrompt_ = NO; |
| 522 |
| 523 [self recordAuthorizationAction:kAuthorizationActionPermanentlyDenied]; |
| 524 break; |
| 525 |
| 526 case kCLAuthorizationStatusAuthorizedAlways: |
| 527 case kCLAuthorizationStatusAuthorizedWhenInUse: |
| 528 self.localState.authorizationState = |
| 529 geolocation::kAuthorizationStateAuthorized; |
| 530 systemPrompt_ = NO; |
| 531 |
| 532 base::scoped_nsobject<Tab> tab([weakTabToReload_ retain]); |
| 533 [self addLocationAndReloadTab:tab]; |
| 534 weakTabToReload_.reset(); |
| 535 |
| 536 [self recordAuthorizationAction:kAuthorizationActionAuthorized]; |
| 537 break; |
| 538 } |
| 539 } |
| 540 } |
| 541 |
| 542 #pragma mark - OmniboxGeolocationAuthorizationAlertDelegate |
| 543 |
| 544 - (void)authorizationAlertDidAuthorize: |
| 545 (OmniboxGeolocationAuthorizationAlert*)authorizationAlert { |
| 546 self.localState.authorizationState = |
| 547 geolocation::kAuthorizationStateAuthorized; |
| 548 |
| 549 base::scoped_nsobject<Tab> tab([weakTabToReload_ retain]); |
| 550 [self addLocationAndReloadTab:tab]; |
| 551 |
| 552 // Just resetting |authorizationAlert_| leads to a user-after-free crash |
| 553 // presumably due to a UIKit bug. Making authorizationAlert_ autorelease |
| 554 // will keep it alive long enough to avoid the crash. See crbug.com/381235 |
| 555 authorizationAlert_.autorelease(); |
| 556 weakTabToReload_.reset(); |
| 557 |
| 558 [self recordAuthorizationAction:kAuthorizationActionAuthorized]; |
| 559 } |
| 560 |
| 561 - (void)authorizationAlertDidCancel: |
| 562 (OmniboxGeolocationAuthorizationAlert*)authorizationAlert { |
| 563 // Leave authorization state as undetermined (not kAuthorizationStateDenied). |
| 564 // We won't use location, but we'll still be able to prompt at the next |
| 565 // application update. |
| 566 |
| 567 // Just resetting |authorizationAlert_| leads to a user-after-free crash |
| 568 // presumably due to a UIKit bug. Making authorizationAlert_ autorelease |
| 569 // will keep it alive long enough to avoid the crash. See crbug.com/381235 |
| 570 authorizationAlert_.autorelease(); |
| 571 weakTabToReload_.reset(); |
| 572 |
| 573 [self recordAuthorizationAction:kAuthorizationActionDenied]; |
| 574 } |
| 575 |
| 576 #pragma mark - OmniboxGeolocationController+Testing |
| 577 |
| 578 - (void)setLocalState:(OmniboxGeolocationLocalState*)localState { |
| 579 localState_.reset([localState retain]); |
| 580 } |
| 581 |
| 582 - (void)setLocationManager:(LocationManager*)locationManager { |
| 583 locationManager_.reset([locationManager retain]); |
| 584 } |
| 585 |
| 586 @end |
OLD | NEW |