Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.omnibox.geo; | 5 package org.chromium.chrome.browser.omnibox.geo; |
| 6 | 6 |
| 7 import android.Manifest; | 7 import android.Manifest; |
| 8 import android.content.pm.PackageManager; | 8 import android.content.pm.PackageManager; |
| 9 import android.location.Location; | 9 import android.location.Location; |
| 10 import android.location.LocationManager; | 10 import android.location.LocationManager; |
| 11 import android.net.Uri; | 11 import android.net.Uri; |
| 12 import android.os.Build; | 12 import android.os.Build; |
| 13 import android.os.Process; | 13 import android.os.Process; |
| 14 import android.os.SystemClock; | 14 import android.os.SystemClock; |
| 15 import android.provider.Settings; | 15 import android.provider.Settings; |
| 16 import android.support.annotation.IntDef; | 16 import android.support.annotation.IntDef; |
| 17 import android.util.Base64; | 17 import android.util.Base64; |
| 18 | 18 |
| 19 import com.google.protobuf.nano.MessageNano; | 19 import com.google.protobuf.nano.MessageNano; |
| 20 | 20 |
| 21 import org.chromium.base.ApiCompatibilityUtils; | 21 import org.chromium.base.ApiCompatibilityUtils; |
| 22 import org.chromium.base.ContextUtils; | 22 import org.chromium.base.ContextUtils; |
| 23 import org.chromium.base.Log; | 23 import org.chromium.base.Log; |
| 24 import org.chromium.base.VisibleForTesting; | |
| 24 import org.chromium.base.annotations.CalledByNative; | 25 import org.chromium.base.annotations.CalledByNative; |
| 25 import org.chromium.base.annotations.SuppressFBWarnings; | 26 import org.chromium.base.annotations.SuppressFBWarnings; |
| 26 import org.chromium.base.metrics.RecordHistogram; | 27 import org.chromium.base.metrics.RecordHistogram; |
| 27 import org.chromium.chrome.browser.ChromeFeatureList; | 28 import org.chromium.chrome.browser.ChromeFeatureList; |
| 28 import org.chromium.chrome.browser.UrlConstants; | 29 import org.chromium.chrome.browser.UrlConstants; |
| 30 import org.chromium.chrome.browser.omnibox.geo.VisibleNetworks.VisibleCell; | |
| 31 import org.chromium.chrome.browser.omnibox.geo.VisibleNetworks.VisibleWifi; | |
| 29 import org.chromium.chrome.browser.preferences.website.ContentSetting; | 32 import org.chromium.chrome.browser.preferences.website.ContentSetting; |
| 30 import org.chromium.chrome.browser.preferences.website.GeolocationInfo; | 33 import org.chromium.chrome.browser.preferences.website.GeolocationInfo; |
| 31 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge; | 34 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge; |
| 32 import org.chromium.chrome.browser.tab.Tab; | 35 import org.chromium.chrome.browser.tab.Tab; |
| 33 import org.chromium.chrome.browser.util.UrlUtilities; | 36 import org.chromium.chrome.browser.util.UrlUtilities; |
| 34 | 37 |
| 35 import java.lang.annotation.Retention; | 38 import java.lang.annotation.Retention; |
| 36 import java.lang.annotation.RetentionPolicy; | 39 import java.lang.annotation.RetentionPolicy; |
| 40 import java.util.Arrays; | |
| 41 import java.util.HashSet; | |
| 37 import java.util.Locale; | 42 import java.util.Locale; |
| 43 import java.util.Set; | |
| 38 import java.util.concurrent.TimeUnit; | 44 import java.util.concurrent.TimeUnit; |
| 39 | 45 |
| 46 import javax.annotation.Nullable; | |
| 47 | |
| 40 /** | 48 /** |
| 41 * Provides methods for building the X-Geo HTTP header, which provides device lo cation to a server | 49 * Provides methods for building the X-Geo HTTP header, which provides device lo cation to a server |
| 42 * when making an HTTP request. | 50 * when making an HTTP request. |
| 43 * | 51 * |
| 44 * X-Geo header spec: https://goto.google.com/xgeospec. | 52 * X-Geo header spec: https://goto.google.com/xgeospec. |
| 45 */ | 53 */ |
| 46 public class GeolocationHeader { | 54 public class GeolocationHeader { |
| 47 private static final String TAG = "GeolocationHeader"; | 55 private static final String TAG = "GeolocationHeader"; |
| 48 | 56 |
| 49 // Values for the histogram Geolocation.HeaderSentOrNot. Values 1, 5, 6, and 7 are defined in | 57 // Values for the histogram Geolocation.HeaderSentOrNot. Values 1, 5, 6, and 7 are defined in |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 141 UMA_PERM_GPS_ONLY_APP_BLOCKED_DOMAIN_BLOCKED, UMA_PERM_MASTER_OFF_AP P_YES_DOMAIN_YES, | 149 UMA_PERM_GPS_ONLY_APP_BLOCKED_DOMAIN_BLOCKED, UMA_PERM_MASTER_OFF_AP P_YES_DOMAIN_YES, |
| 142 UMA_PERM_MASTER_OFF_APP_YES_DOMAIN_PROMPT, UMA_PERM_MASTER_OFF_APP_Y ES_DOMAIN_BLOCKED, | 150 UMA_PERM_MASTER_OFF_APP_YES_DOMAIN_PROMPT, UMA_PERM_MASTER_OFF_APP_Y ES_DOMAIN_BLOCKED, |
| 143 UMA_PERM_MASTER_OFF_APP_PROMPT_DOMAIN_YES, UMA_PERM_MASTER_OFF_APP_P ROMPT_DOMAIN_PROMPT, | 151 UMA_PERM_MASTER_OFF_APP_PROMPT_DOMAIN_YES, UMA_PERM_MASTER_OFF_APP_P ROMPT_DOMAIN_PROMPT, |
| 144 UMA_PERM_MASTER_OFF_APP_PROMPT_DOMAIN_BLOCKED, | 152 UMA_PERM_MASTER_OFF_APP_PROMPT_DOMAIN_BLOCKED, |
| 145 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_YES, | 153 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_YES, |
| 146 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_PROMPT, | 154 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_PROMPT, |
| 147 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_BLOCKED, UMA_PERM_UNSUITABLE_ URL, | 155 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_BLOCKED, UMA_PERM_UNSUITABLE_ URL, |
| 148 UMA_PERM_NOT_HTTPS}) | 156 UMA_PERM_NOT_HTTPS}) |
| 149 public @interface UmaPermission {} | 157 public @interface UmaPermission {} |
| 150 | 158 |
| 151 private static final int LOCATION_SOURCE_HIGH_ACCURACY = 0; | 159 @VisibleForTesting |
| 152 private static final int LOCATION_SOURCE_BATTERY_SAVING = 1; | 160 static final int LOCATION_SOURCE_HIGH_ACCURACY = 0; |
| 153 private static final int LOCATION_SOURCE_GPS_ONLY = 2; | 161 @VisibleForTesting |
| 154 private static final int LOCATION_SOURCE_MASTER_OFF = 3; | 162 static final int LOCATION_SOURCE_BATTERY_SAVING = 1; |
| 163 @VisibleForTesting | |
| 164 static final int LOCATION_SOURCE_GPS_ONLY = 2; | |
| 165 @VisibleForTesting | |
| 166 static final int LOCATION_SOURCE_MASTER_OFF = 3; | |
| 155 @Retention(RetentionPolicy.SOURCE) | 167 @Retention(RetentionPolicy.SOURCE) |
| 156 @IntDef({LOCATION_SOURCE_HIGH_ACCURACY, LOCATION_SOURCE_BATTERY_SAVING, | 168 @IntDef({LOCATION_SOURCE_HIGH_ACCURACY, LOCATION_SOURCE_BATTERY_SAVING, |
| 157 LOCATION_SOURCE_GPS_ONLY, LOCATION_SOURCE_MASTER_OFF}) | 169 LOCATION_SOURCE_GPS_ONLY, LOCATION_SOURCE_MASTER_OFF}) |
| 158 private @interface LocationSource {} | 170 private @interface LocationSource {} |
| 159 | 171 |
| 160 private static final int PERMISSION_GRANTED = 0; | 172 private static final int PERMISSION_GRANTED = 0; |
| 161 private static final int PERMISSION_PROMPT = 1; | 173 private static final int PERMISSION_PROMPT = 1; |
| 162 private static final int PERMISSION_BLOCKED = 2; | 174 private static final int PERMISSION_BLOCKED = 2; |
| 163 @Retention(RetentionPolicy.SOURCE) | 175 @Retention(RetentionPolicy.SOURCE) |
| 164 @IntDef({PERMISSION_GRANTED, PERMISSION_PROMPT, PERMISSION_BLOCKED}) | 176 @IntDef({PERMISSION_GRANTED, PERMISSION_PROMPT, PERMISSION_BLOCKED}) |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 184 | 196 |
| 185 /** The maximum age in milliseconds of a location before we'll request a ref resh. */ | 197 /** The maximum age in milliseconds of a location before we'll request a ref resh. */ |
| 186 private static final int REFRESH_LOCATION_AGE = 5 * 60 * 1000; // 5 minutes | 198 private static final int REFRESH_LOCATION_AGE = 5 * 60 * 1000; // 5 minutes |
| 187 | 199 |
| 188 /** The X-Geo header prefix, preceding any location descriptors */ | 200 /** The X-Geo header prefix, preceding any location descriptors */ |
| 189 private static final String XGEO_HEADER_PREFIX = "X-Geo: "; | 201 private static final String XGEO_HEADER_PREFIX = "X-Geo: "; |
| 190 | 202 |
| 191 /** The location descriptor prefix used in the X-Geo header to specify a pro to wire encoding */ | 203 /** The location descriptor prefix used in the X-Geo header to specify a pro to wire encoding */ |
| 192 private static final String LOCATION_PROTO_PREFIX = "w "; | 204 private static final String LOCATION_PROTO_PREFIX = "w "; |
| 193 | 205 |
| 206 /** The location descriptor separator used in the X-Geo header to upload mul tiple descriptors */ | |
| 207 private static final String LOCATION_PROTO_SEPARATOR = " "; | |
| 208 | |
| 194 /** The location descriptor prefix used in the X-Geo header to specify an AS CII encoding */ | 209 /** The location descriptor prefix used in the X-Geo header to specify an AS CII encoding */ |
| 195 private static final String LOCATION_ASCII_PREFIX = "a "; | 210 private static final String LOCATION_ASCII_PREFIX = "a "; |
| 196 | 211 |
| 197 /** The time of the first location refresh. Contains Long.MAX_VALUE if not s et. */ | 212 /** The time of the first location refresh. Contains Long.MAX_VALUE if not s et. */ |
| 198 private static long sFirstLocationTime = Long.MAX_VALUE; | 213 private static long sFirstLocationTime = Long.MAX_VALUE; |
| 199 | 214 |
| 215 /** Present in WiFi SSID that should not be mapped */ | |
| 216 private static final String SSID_NOMAP = "_nomap"; | |
| 217 | |
| 218 /** Present in WiFi SSID that opted out */ | |
| 219 private static final String SSID_OPTOUT = "_optout"; | |
| 220 | |
| 221 private static int sLocationSourceForTesting; | |
| 222 private static boolean sUseLocationSourceForTesting; | |
| 223 | |
| 224 private static boolean sAppPermissionGrantedForTesting; | |
| 225 private static boolean sUseAppPermissionGrantedForTesting; | |
| 226 | |
| 200 /** | 227 /** |
| 201 * Requests a location refresh so that a valid location will be available fo r constructing | 228 * Requests a location refresh so that a valid location will be available fo r constructing |
| 202 * an X-Geo header in the near future (i.e. within 5 minutes). | 229 * an X-Geo header in the near future (i.e. within 5 minutes). |
| 203 */ | 230 */ |
| 204 public static void primeLocationForGeoHeader() { | 231 public static void primeLocationForGeoHeader() { |
| 205 if (!hasGeolocationPermission()) return; | 232 if (!hasGeolocationPermission()) return; |
| 206 | 233 |
| 207 if (sFirstLocationTime == Long.MAX_VALUE) { | 234 if (sFirstLocationTime == Long.MAX_VALUE) { |
| 208 sFirstLocationTime = SystemClock.elapsedRealtime(); | 235 sFirstLocationTime = SystemClock.elapsedRealtime(); |
| 209 } | 236 } |
| 210 GeolocationTracker.refreshLastKnownLocation( | 237 GeolocationTracker.refreshLastKnownLocation( |
| 211 ContextUtils.getApplicationContext(), REFRESH_LOCATION_AGE); | 238 ContextUtils.getApplicationContext(), REFRESH_LOCATION_AGE); |
| 239 | |
| 240 // Only refresh visible networks if enabled. | |
| 241 if (ChromeFeatureList.isEnabled(ChromeFeatureList.XGEO_VISIBLE_NETWORKS) ) { | |
| 242 VisibleNetworksTracker.refreshLastKnownVisibleNetworks( | |
| 243 ContextUtils.getApplicationContext(), REFRESH_LOCATION_AGE); | |
| 244 } | |
| 212 } | 245 } |
| 213 | 246 |
| 214 /** | 247 /** |
| 215 * Returns whether the X-Geo header is allowed to be sent for the current UR L. | 248 * Returns whether the X-Geo header is allowed to be sent for the current UR L. |
| 216 * | 249 * |
| 217 * @param url The URL of the request with which this header will be sent. | 250 * @param url The URL of the request with which this header will be sent. |
| 218 * @param isIncognito Whether the request will happen in an incognito tab. | 251 * @param isIncognito Whether the request will happen in an incognito tab. |
| 219 */ | 252 */ |
| 220 public static boolean isGeoHeaderEnabledForUrl(String url, boolean isIncogni to) { | 253 public static boolean isGeoHeaderEnabledForUrl(String url, boolean isIncogni to) { |
| 221 return geoHeaderStateForUrl(url, isIncognito, false) == HEADER_ENABLED; | 254 return geoHeaderStateForUrl(url, isIncognito, false) == HEADER_ENABLED; |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 252 * 2. The url is a google search URL (e.g. www.google.co.uk/search?q=cars), and | 285 * 2. The url is a google search URL (e.g. www.google.co.uk/search?q=cars), and |
| 253 * 3. The user has not disabled sharing location with this url, and | 286 * 3. The user has not disabled sharing location with this url, and |
| 254 * 4. There is a valid and recent location available. | 287 * 4. There is a valid and recent location available. |
| 255 * | 288 * |
| 256 * Returns null otherwise. | 289 * Returns null otherwise. |
| 257 * | 290 * |
| 258 * @param url The URL of the request with which this header will be sent. | 291 * @param url The URL of the request with which this header will be sent. |
| 259 * @param tab The Tab currently being accessed. | 292 * @param tab The Tab currently being accessed. |
| 260 * @return The X-Geo header string or null. | 293 * @return The X-Geo header string or null. |
| 261 */ | 294 */ |
| 262 public static String getGeoHeader(String url, Tab tab) { | 295 public static String getGeoHeader(String url, Tab tab) { |
|
dougt
2017/05/18 02:33:15
This method is getting a bit unwieldy. We should
lbargu
2017/05/18 15:14:09
Agree. Already added all new functionality to priv
| |
| 263 boolean isIncognito = tab.isIncognito(); | 296 boolean isIncognito = tab.isIncognito(); |
| 264 boolean locationAttached = true; | 297 Location locationToAttach = null; |
| 265 Location location = null; | 298 VisibleNetworks visibleNetworksToAttach = null; |
| 266 long locationAge = Long.MAX_VALUE; | 299 long locationAge = Long.MAX_VALUE; |
| 267 @HeaderState int headerState = geoHeaderStateForUrl(url, isIncognito, tr ue); | 300 @HeaderState int headerState = geoHeaderStateForUrl(url, isIncognito, tr ue); |
| 268 boolean isXGeoVisibleNetworksEnabled = | 301 boolean isXGeoVisibleNetworksEnabled = |
| 269 ChromeFeatureList.isEnabled(ChromeFeatureList.XGEO_VISIBLE_NETWO RKS); | 302 ChromeFeatureList.isEnabled(ChromeFeatureList.XGEO_VISIBLE_NETWO RKS); |
|
dougt
2017/05/18 02:33:15
I should have caught this in the last review, but
lbargu
2017/05/18 15:14:09
Done.
| |
| 270 if (headerState == HEADER_ENABLED) { | 303 if (headerState == HEADER_ENABLED) { |
| 271 // Only send X-Geo header if there's a fresh location available. | 304 // Only send X-Geo header if there's a fresh location available. |
| 272 // Use flag controlling visible network changes to decide whether GP S location should be | 305 // Use flag controlling visible network changes to decide whether GP S location should be |
| 273 // included as a fallback. | 306 // included as a fallback. |
| 274 location = GeolocationTracker.getLastKnownLocation( | 307 locationToAttach = GeolocationTracker.getLastKnownLocation( |
|
dougt
2017/05/18 02:33:15
Since this is on the hot path, I think we should c
lbargu
2017/05/18 15:14:08
Added TODO. Will add metrics in follow up.
For now
| |
| 275 ContextUtils.getApplicationContext(), isXGeoVisibleNetworksE nabled); | 308 ContextUtils.getApplicationContext(), isXGeoVisibleNetworksE nabled); |
| 276 if (location == null) { | 309 if (locationToAttach == null) { |
| 277 recordHistogram(UMA_LOCATION_NOT_AVAILABLE); | 310 recordHistogram(UMA_LOCATION_NOT_AVAILABLE); |
| 278 locationAttached = false; | |
| 279 } else { | 311 } else { |
| 280 locationAge = GeolocationTracker.getLocationAge(location); | 312 locationAge = GeolocationTracker.getLocationAge(locationToAttach ); |
| 281 if (locationAge > MAX_LOCATION_AGE) { | 313 if (locationAge > MAX_LOCATION_AGE) { |
| 314 // Do not attach the location | |
| 282 recordHistogram(UMA_LOCATION_STALE); | 315 recordHistogram(UMA_LOCATION_STALE); |
| 283 locationAttached = false; | 316 locationToAttach = null; |
| 317 } else { | |
| 318 recordHistogram(UMA_HEADER_SENT); | |
| 284 } | 319 } |
| 285 } | 320 } |
| 286 } else { | 321 |
| 287 locationAttached = false; | 322 // The header state is enabled, so this means we have app permission s, and the url is |
| 323 // allowed to receive location. Before attempting to attach visible networks, check if | |
| 324 // the device location settigs are in High Accuracy or Battery Savin g, meaning the | |
| 325 // device is allowed to use network-based location. | |
| 326 if (isXGeoVisibleNetworksEnabled | |
| 327 && (getLocationSource() == LOCATION_SOURCE_HIGH_ACCURACY | |
| 328 || getLocationSource() == LOCATION_SOURCE_BATTERY _SAVING)) { | |
| 329 if (locationToAttach == null | |
| 330 || GeolocationTracker.getLocationAge(locationToAttach) | |
| 331 > REFRESH_LOCATION_AGE) { | |
|
dougt
2017/05/18 02:33:15
I would have turned this logic around a bit and ju
lbargu
2017/05/18 15:14:09
Moved checks to private methods. Should be easier
| |
| 332 // We should attach visible networks in this case, since per missions are good | |
| 333 // but we didn't attach location or it's older than the refr esh time. | |
|
dougt
2017/05/18 02:33:15
no need for this comment.
lbargu
2017/05/18 15:14:08
Done.
| |
| 334 visibleNetworksToAttach = VisibleNetworksTracker.getLastKnow nVisibleNetworks( | |
| 335 ContextUtils.getApplicationContext()); | |
| 336 } | |
| 337 } | |
| 288 } | 338 } |
| 289 | 339 |
| 290 @LocationSource int locationSource = getLocationSource(); | 340 @LocationSource int locationSource = getLocationSource(); |
| 291 @Permission int appPermission = getGeolocationPermission(tab); | 341 @Permission int appPermission = getGeolocationPermission(tab); |
| 292 @Permission int domainPermission = getDomainPermission(url, isIncognito) ; | 342 @Permission int domainPermission = getDomainPermission(url, isIncognito) ; |
| 293 | 343 |
| 294 // Record the permission state with a histogram. | 344 // Record the permission state with a histogram. |
| 295 recordPermissionHistogram( | 345 recordPermissionHistogram(locationSource, appPermission, domainPermissio n, |
| 296 locationSource, appPermission, domainPermission, locationAttache d, headerState); | 346 locationToAttach != null, headerState); |
| 297 | 347 |
| 298 if (locationSource != LOCATION_SOURCE_MASTER_OFF && appPermission != PER MISSION_BLOCKED | 348 if (locationSource != LOCATION_SOURCE_MASTER_OFF && appPermission != PER MISSION_BLOCKED |
| 299 && domainPermission != PERMISSION_BLOCKED && !isIncognito) { | 349 && domainPermission != PERMISSION_BLOCKED && !isIncognito) { |
| 300 // Record the Location Age with a histogram. | 350 // Record the Location Age with a histogram. |
| 301 recordLocationAgeHistogram(locationSource, locationAge); | 351 recordLocationAgeHistogram(locationSource, locationAge); |
| 302 long duration = sFirstLocationTime == Long.MAX_VALUE | 352 long duration = sFirstLocationTime == Long.MAX_VALUE |
| 303 ? 0 | 353 ? 0 |
| 304 : SystemClock.elapsedRealtime() - sFirstLocationTime; | 354 : SystemClock.elapsedRealtime() - sFirstLocationTime; |
| 305 // Record the Time Listening with a histogram. | 355 // Record the Time Listening with a histogram. |
| 306 recordTimeListeningHistogram(locationSource, locationAttached, durat ion); | 356 recordTimeListeningHistogram(locationSource, locationToAttach != nul l, duration); |
| 307 } | 357 } |
| 308 | 358 |
| 309 // Note that strictly speaking "location == null" is not needed here as the | |
| 310 // logic above prevents location being null when locationAttached is tru e. | |
| 311 // It is here to prevent problems if the logic above is changed. | |
| 312 if (!locationAttached || location == null) return null; | |
| 313 | |
| 314 recordHistogram(UMA_HEADER_SENT); | |
| 315 | |
| 316 // Timestamp in microseconds since the UNIX epoch. | |
| 317 long timestamp = location.getTime() * 1000; | |
| 318 // Latitude times 1e7. | |
| 319 int latitudeE7 = (int) (location.getLatitude() * 10000000); | |
| 320 // Longitude times 1e7. | |
| 321 int longitudeE7 = (int) (location.getLongitude() * 10000000); | |
| 322 // Radius of 68% accuracy in mm. | |
| 323 int radius = (int) (location.getAccuracy() * 1000); | |
| 324 | |
| 325 // Encode location using ascii protobuf format followed by base64 encodi ng. | |
| 326 // https://goto.google.com/partner_location_proto | |
| 327 String locationAscii = String.format(Locale.US, | |
| 328 "role:1 producer:12 timestamp:%d latlng{latitude_e7:%d longitude _e7:%d} radius:%d", | |
| 329 timestamp, latitudeE7, longitudeE7, radius); | |
| 330 String locationAsciiEncoding = | |
| 331 new String(Base64.encode(locationAscii.getBytes(), Base64.NO_WRA P)); | |
| 332 | 359 |
| 333 if (!isXGeoVisibleNetworksEnabled) { | 360 if (!isXGeoVisibleNetworksEnabled) { |
| 334 return XGEO_HEADER_PREFIX + LOCATION_ASCII_PREFIX + locationAsciiEnc oding; | 361 if (locationToAttach != null) { |
|
dougt
2017/05/18 02:33:15
nit: avoid the block, and just return early:
if (
lbargu
2017/05/18 15:14:09
Done.
| |
| 362 // Timestamp in microseconds since the UNIX epoch. | |
| 363 long timestamp = locationToAttach.getTime() * 1000; | |
| 364 // Latitude times 1e7. | |
| 365 int latitudeE7 = (int) (locationToAttach.getLatitude() * 1000000 0); | |
| 366 // Longitude times 1e7. | |
| 367 int longitudeE7 = (int) (locationToAttach.getLongitude() * 10000 000); | |
| 368 // Radius of 68% accuracy in mm. | |
| 369 int radius = (int) (locationToAttach.getAccuracy() * 1000); | |
| 370 | |
| 371 // Encode location using ascii protobuf format followed by base6 4 encoding. | |
| 372 // https://goto.google.com/partner_location_proto | |
| 373 String locationAscii = String.format(Locale.US, | |
| 374 "role:1 producer:12 timestamp:%d latlng{latitude_e7:%d l ongitude_e7:%d}" | |
| 375 + " radius:%d", | |
| 376 timestamp, latitudeE7, longitudeE7, radius); | |
| 377 String locationAsciiEncoding = | |
| 378 new String(Base64.encode(locationAscii.getBytes(), Base6 4.NO_WRAP)); | |
| 379 return XGEO_HEADER_PREFIX + LOCATION_ASCII_PREFIX + locationAsci iEncoding; | |
| 380 } | |
| 381 return null; | |
| 335 } | 382 } |
| 336 | 383 |
| 337 // Create a LatLng for the coordinates. | 384 // Proto encoding |
| 338 PartnerLocationDescriptor.LatLng latlng = new PartnerLocationDescriptor. LatLng(); | 385 String locationProtoEncoding = encodeProtoLocation(locationToAttach); |
| 339 latlng.latitudeE7 = latitudeE7; | 386 String visibleNetworksProtoEncoding = encodeProtoVisibleNetworks(visible NetworksToAttach); |
| 340 latlng.longitudeE7 = longitudeE7; | |
| 341 | 387 |
| 342 // Populate a LocationDescriptor with the LatLng. | 388 if (locationProtoEncoding != null) { |
| 343 PartnerLocationDescriptor.LocationDescriptor locationDescriptor = | 389 if (visibleNetworksProtoEncoding != null) { |
| 344 new PartnerLocationDescriptor.LocationDescriptor(); | 390 return XGEO_HEADER_PREFIX + LOCATION_PROTO_PREFIX + locationProt oEncoding |
| 345 locationDescriptor.latlng = latlng; | 391 + LOCATION_PROTO_SEPARATOR + visibleNetworksProtoEncodin g; |
| 346 // Include role, producer, timestamp and radius. | 392 } |
| 347 locationDescriptor.role = PartnerLocationDescriptor.CURRENT_LOCATION; | 393 return XGEO_HEADER_PREFIX + LOCATION_PROTO_PREFIX + locationProtoEnc oding; |
| 348 locationDescriptor.producer = PartnerLocationDescriptor.DEVICE_LOCATION; | 394 } |
|
dougt
2017/05/18 02:33:15
Would something like the following work?
if (loca
Ted C
2017/05/18 14:43:03
We should use a StringBuilder if we go with this a
lbargu
2017/05/18 15:14:08
Done.
lbargu
2017/05/18 15:14:09
Done with builder.
| |
| 349 locationDescriptor.timestamp = timestamp; | 395 if (visibleNetworksProtoEncoding != null) { |
| 350 locationDescriptor.radius = (float) radius; | 396 return XGEO_HEADER_PREFIX + LOCATION_PROTO_PREFIX + visibleNetworksP rotoEncoding; |
| 351 | 397 } |
| 352 String locationProtoEncoding = Base64.encodeToString( | 398 return null; |
| 353 MessageNano.toByteArray(locationDescriptor), Base64.NO_WRAP | Ba se64.URL_SAFE); | |
| 354 | |
| 355 return XGEO_HEADER_PREFIX + LOCATION_PROTO_PREFIX + locationProtoEncodin g; | |
| 356 } | 399 } |
| 357 | 400 |
| 358 @CalledByNative | 401 @CalledByNative |
| 359 static boolean hasGeolocationPermission() { | 402 static boolean hasGeolocationPermission() { |
| 403 if (sUseAppPermissionGrantedForTesting) return sAppPermissionGrantedForT esting; | |
|
Ted C
2017/05/18 14:43:03
can we use
http://robolectric.org/javadoc/latest/o
lbargu
2017/05/18 15:14:08
It's not available in chromium source code?
Ted C
2017/05/18 15:25:29
Indeed it is not...sadness.
Looking at what is in
lbargu
2017/05/18 15:56:28
Tried, but a time drainer, no success :(
| |
| 360 int pid = Process.myPid(); | 404 int pid = Process.myPid(); |
| 361 int uid = Process.myUid(); | 405 int uid = Process.myUid(); |
| 362 if (ApiCompatibilityUtils.checkPermission(ContextUtils.getApplicationCon text(), | 406 if (ApiCompatibilityUtils.checkPermission(ContextUtils.getApplicationCon text(), |
| 363 Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) | 407 Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) |
| 364 != PackageManager.PERMISSION_GRANTED) { | 408 != PackageManager.PERMISSION_GRANTED) { |
| 365 return false; | 409 return false; |
| 366 } | 410 } |
| 367 | 411 |
| 368 // Work around a bug in OnePlus2 devices running Lollipop, where the NET WORK_PROVIDER | 412 // Work around a bug in OnePlus2 devices running Lollipop, where the NET WORK_PROVIDER |
| 369 // incorrectly requires FINE_LOCATION permission (it should only require COARSE_LOCATION | 413 // incorrectly requires FINE_LOCATION permission (it should only require COARSE_LOCATION |
| 370 // permission). http://crbug.com/580733 | 414 // permission). http://crbug.com/580733 |
| 371 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M | 415 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M |
| 372 && ApiCompatibilityUtils.checkPermission(ContextUtils.getApplica tionContext(), | 416 && ApiCompatibilityUtils.checkPermission(ContextUtils.getApplica tionContext(), |
| 373 Manifest.permission.ACCESS_FINE_LOCATION, pid, uid) | 417 Manifest.permission.ACCESS_FINE_LOCATION, pid, uid) |
| 374 != PackageManager.PERMISSION_GRANTED) { | 418 != PackageManager.PERMISSION_GRANTED) { |
| 375 return false; | 419 return false; |
| 376 } | 420 } |
| 377 | 421 |
| 378 return true; | 422 return true; |
| 379 } | 423 } |
| 380 | 424 |
| 381 /** | 425 /** |
| 382 * Returns the app level geolocation permission. | 426 * Returns the app level geolocation permission. |
| 383 * This permission can be either granted, blocked or prompt. | 427 * This permission can be either granted, blocked or prompt. |
| 384 */ | 428 */ |
| 385 @Permission | 429 @Permission |
| 386 static int getGeolocationPermission(Tab tab) { | 430 static int getGeolocationPermission(Tab tab) { |
| 431 if (sUseAppPermissionGrantedForTesting) { | |
| 432 return sAppPermissionGrantedForTesting ? PERMISSION_GRANTED : PERMIS SION_BLOCKED; | |
| 433 } | |
| 387 if (hasGeolocationPermission()) return PERMISSION_GRANTED; | 434 if (hasGeolocationPermission()) return PERMISSION_GRANTED; |
| 388 return tab.getWindowAndroid().canRequestPermission( | 435 return tab.getWindowAndroid().canRequestPermission( |
| 389 Manifest.permission.ACCESS_COARSE_LOCATION) | 436 Manifest.permission.ACCESS_COARSE_LOCATION) |
| 390 ? PERMISSION_PROMPT | 437 ? PERMISSION_PROMPT |
| 391 : PERMISSION_BLOCKED; | 438 : PERMISSION_BLOCKED; |
| 392 } | 439 } |
| 393 | 440 |
| 394 /** | 441 /** |
| 395 * Returns true if the user has disabled sharing their location with url (e. g. via the | 442 * Returns true if the user has disabled sharing their location with url (e. g. via the |
| 396 * geolocation infobar). | 443 * geolocation infobar). |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 409 /** | 456 /** |
| 410 * Returns the location permission for sharing their location with url (e.g. via the | 457 * Returns the location permission for sharing their location with url (e.g. via the |
| 411 * geolocation infobar). | 458 * geolocation infobar). |
| 412 */ | 459 */ |
| 413 static ContentSetting locationContentSettingForUrl(Uri uri, boolean isIncogn ito) { | 460 static ContentSetting locationContentSettingForUrl(Uri uri, boolean isIncogn ito) { |
| 414 GeolocationInfo locationSettings = new GeolocationInfo(uri.toString(), n ull, isIncognito); | 461 GeolocationInfo locationSettings = new GeolocationInfo(uri.toString(), n ull, isIncognito); |
| 415 ContentSetting locationPermission = locationSettings.getContentSetting() ; | 462 ContentSetting locationPermission = locationSettings.getContentSetting() ; |
| 416 return locationPermission; | 463 return locationPermission; |
| 417 } | 464 } |
| 418 | 465 |
| 466 @VisibleForTesting | |
| 467 static void setLocationSourceForTesting(int locationSourceForTesting) { | |
| 468 sLocationSourceForTesting = locationSourceForTesting; | |
| 469 sUseLocationSourceForTesting = true; | |
| 470 } | |
| 471 | |
| 472 @VisibleForTesting | |
| 473 static void setAppPermissionGrantedForTesting(boolean appPermissionGrantedFo rTesting) { | |
| 474 sAppPermissionGrantedForTesting = appPermissionGrantedForTesting; | |
| 475 sUseAppPermissionGrantedForTesting = true; | |
| 476 } | |
| 477 | |
| 419 /** Records a data point for the Geolocation.HeaderSentOrNot histogram. */ | 478 /** Records a data point for the Geolocation.HeaderSentOrNot histogram. */ |
| 420 private static void recordHistogram(int result) { | 479 private static void recordHistogram(int result) { |
| 421 RecordHistogram.recordEnumeratedHistogram("Geolocation.HeaderSentOrNot", result, UMA_MAX); | 480 RecordHistogram.recordEnumeratedHistogram("Geolocation.HeaderSentOrNot", result, UMA_MAX); |
| 422 } | 481 } |
| 423 | 482 |
| 424 /** Returns the location source. */ | 483 /** Returns the location source. */ |
| 425 @LocationSource | 484 @LocationSource |
| 426 // We should replace our usage of LOCATION_PROVIDERS_ALLOWED when the min AP I is 19. | 485 // We should replace our usage of LOCATION_PROVIDERS_ALLOWED when the min AP I is 19. |
| 427 @SuppressWarnings("deprecation") | 486 @SuppressWarnings("deprecation") |
| 428 private static int getLocationSource() { | 487 private static int getLocationSource() { |
| 488 if (sUseLocationSourceForTesting) return sLocationSourceForTesting; | |
| 489 | |
| 429 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | 490 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| 430 int locationMode; | 491 int locationMode; |
| 431 try { | 492 try { |
| 432 locationMode = Settings.Secure.getInt( | 493 locationMode = Settings.Secure.getInt( |
| 433 ContextUtils.getApplicationContext().getContentResolver( ), | 494 ContextUtils.getApplicationContext().getContentResolver( ), |
| 434 Settings.Secure.LOCATION_MODE); | 495 Settings.Secure.LOCATION_MODE); |
| 435 } catch (Settings.SettingNotFoundException e) { | 496 } catch (Settings.SettingNotFoundException e) { |
| 436 Log.e(TAG, "Error getting the LOCATION_MODE"); | 497 Log.e(TAG, "Error getting the LOCATION_MODE"); |
| 437 return LOCATION_SOURCE_MASTER_OFF; | 498 return LOCATION_SOURCE_MASTER_OFF; |
| 438 } | 499 } |
| (...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 668 Log.e(TAG, "Unexpected locationSource: " + locationSource); | 729 Log.e(TAG, "Unexpected locationSource: " + locationSource); |
| 669 assert false : "Unexpected locationSource: " + locationSource; | 730 assert false : "Unexpected locationSource: " + locationSource; |
| 670 return; | 731 return; |
| 671 } | 732 } |
| 672 long durationSeconds = durationMillis / 1000; | 733 long durationSeconds = durationMillis / 1000; |
| 673 int duration = durationSeconds >= (long) Integer.MAX_VALUE ? Integer.MAX _VALUE | 734 int duration = durationSeconds >= (long) Integer.MAX_VALUE ? Integer.MAX _VALUE |
| 674 : (int) durat ionSeconds; | 735 : (int) durat ionSeconds; |
| 675 RecordHistogram.recordCustomCountHistogram( | 736 RecordHistogram.recordCustomCountHistogram( |
| 676 name, duration, 1, LOCATION_AGE_HISTOGRAM_MAX_SECONDS, 50); | 737 name, duration, 1, LOCATION_AGE_HISTOGRAM_MAX_SECONDS, 50); |
| 677 } | 738 } |
| 739 | |
| 740 /** | |
| 741 * Encodes location into proto encoding. | |
| 742 */ | |
| 743 @Nullable | |
| 744 @VisibleForTesting | |
| 745 static String encodeProtoLocation(@Nullable Location location) { | |
| 746 if (location == null) { | |
| 747 // No data to encode. | |
| 748 return null; | |
| 749 } | |
| 750 // Timestamp in microseconds since the UNIX epoch. | |
| 751 long timestamp = location.getTime() * 1000; | |
| 752 // Latitude times 1e7. | |
| 753 int latitudeE7 = (int) (location.getLatitude() * 10000000); | |
| 754 // Longitude times 1e7. | |
| 755 int longitudeE7 = (int) (location.getLongitude() * 10000000); | |
| 756 // Radius of 68% accuracy in mm. | |
| 757 int radius = (int) (location.getAccuracy() * 1000); | |
| 758 | |
| 759 // Create a LatLng for the coordinates. | |
| 760 PartnerLocationDescriptor.LatLng latlng = new PartnerLocationDescriptor. LatLng(); | |
| 761 latlng.latitudeE7 = latitudeE7; | |
| 762 latlng.longitudeE7 = longitudeE7; | |
| 763 | |
| 764 // Populate a LocationDescriptor with the LatLng. | |
| 765 PartnerLocationDescriptor.LocationDescriptor locationDescriptor = | |
| 766 new PartnerLocationDescriptor.LocationDescriptor(); | |
| 767 locationDescriptor.latlng = latlng; | |
| 768 // Include role, producer, timestamp and radius. | |
| 769 locationDescriptor.role = PartnerLocationDescriptor.CURRENT_LOCATION; | |
| 770 locationDescriptor.producer = PartnerLocationDescriptor.DEVICE_LOCATION; | |
| 771 locationDescriptor.timestamp = timestamp; | |
| 772 locationDescriptor.radius = (float) radius; | |
| 773 return encodeLocationDescriptor(locationDescriptor); | |
| 774 } | |
| 775 | |
| 776 /** | |
| 777 * Encodes the given proto location descriptor into a BASE64 URL_SAFE encodi ng. | |
| 778 */ | |
| 779 private static String encodeLocationDescriptor( | |
| 780 PartnerLocationDescriptor.LocationDescriptor locationDescriptor) { | |
| 781 return Base64.encodeToString( | |
| 782 MessageNano.toByteArray(locationDescriptor), Base64.NO_WRAP | Ba se64.URL_SAFE); | |
| 783 } | |
| 784 | |
| 785 /** | |
| 786 * Encodes visible networks in proto encoding. | |
| 787 */ | |
| 788 @Nullable | |
| 789 @VisibleForTesting | |
| 790 static String encodeProtoVisibleNetworks(@Nullable VisibleNetworks visibleNe tworks) { | |
| 791 VisibleNetworks visibleNetworksToEncode = trimVisibleNetworks(visibleNet works); | |
| 792 if (visibleNetworksToEncode == null) { | |
| 793 // No data to encode. | |
| 794 return null; | |
| 795 } | |
| 796 VisibleWifi connectedWifi = visibleNetworksToEncode.connectedWifi(); | |
| 797 VisibleCell connectedCell = visibleNetworksToEncode.connectedCell(); | |
| 798 Set<VisibleWifi> visibleWifis = visibleNetworksToEncode.allVisibleWifis( ); | |
| 799 Set<VisibleCell> visibleCells = visibleNetworksToEncode.allVisibleCells( ); | |
| 800 | |
| 801 int numVisibleNetworks = (connectedWifi != null ? 1 : 0) | |
| 802 + (visibleWifis != null ? visibleWifis.size() : 0) + (connectedC ell != null ? 1 : 0) | |
| 803 + (visibleCells != null ? visibleCells.size() : 0); | |
| 804 if (numVisibleNetworks == 0) { | |
| 805 // No data to encode. | |
| 806 return null; | |
| 807 } | |
| 808 | |
| 809 int i = 0; | |
| 810 PartnerLocationDescriptor.VisibleNetwork[] protoNetworks = | |
| 811 new PartnerLocationDescriptor.VisibleNetwork[numVisibleNetworks] ; | |
| 812 if (connectedWifi != null) { | |
| 813 protoNetworks[i++] = connectedWifi.toProto(true); | |
| 814 } | |
| 815 if (visibleWifis != null) { | |
| 816 for (VisibleWifi visibleWifi : visibleWifis) { | |
| 817 protoNetworks[i++] = visibleWifi.toProto(false); | |
| 818 } | |
| 819 } | |
| 820 if (connectedCell != null) { | |
| 821 protoNetworks[i++] = connectedCell.toProto(true); | |
| 822 } | |
| 823 if (visibleCells != null) { | |
| 824 for (VisibleCell visibleCell : visibleCells) { | |
| 825 protoNetworks[i++] = visibleCell.toProto(false); | |
| 826 } | |
| 827 } | |
| 828 | |
| 829 PartnerLocationDescriptor.LocationDescriptor locationDescriptor = | |
| 830 new PartnerLocationDescriptor.LocationDescriptor(); | |
| 831 locationDescriptor.role = PartnerLocationDescriptor.CURRENT_LOCATION; | |
| 832 locationDescriptor.producer = PartnerLocationDescriptor.DEVICE_LOCATION; | |
| 833 locationDescriptor.visibleNetwork = protoNetworks; | |
| 834 | |
| 835 return encodeLocationDescriptor(locationDescriptor); | |
| 836 } | |
| 837 | |
| 838 @Nullable | |
| 839 @VisibleForTesting | |
| 840 static VisibleNetworks trimVisibleNetworks(@Nullable VisibleNetworks visible Networks) { | |
| 841 if (visibleNetworks == null || visibleNetworks.isEmpty()) { | |
| 842 return null; | |
| 843 } | |
| 844 // Trim visible networks to only include a limited number of visible not -conntected networks | |
| 845 // based on flag. | |
| 846 VisibleCell connectedCell = visibleNetworks.connectedCell(); | |
| 847 VisibleWifi connectedWifi = visibleNetworks.connectedWifi(); | |
| 848 Set<VisibleCell> visibleCells = visibleNetworks.allVisibleCells(); | |
| 849 Set<VisibleWifi> visibleWifis = visibleNetworks.allVisibleWifis(); | |
| 850 VisibleCell extraVisibleCell = null; | |
| 851 VisibleWifi extraVisibleWifi = null; | |
| 852 if (shouldExcludeVisibleWifi(connectedWifi)) { | |
| 853 // Trim the connected wifi. | |
| 854 connectedWifi = null; | |
| 855 } | |
| 856 // Select the extra visible cell. | |
| 857 if (visibleCells != null) { | |
| 858 for (VisibleCell candidateCell : visibleCells) { | |
| 859 if (ApiCompatibilityUtils.objectEquals(connectedCell, candidateC ell)) { | |
| 860 // Do not include this candidate cell, since its already the connected one. | |
| 861 continue; | |
| 862 } | |
| 863 // Add it and since we only want one, stop iterating over other cells. | |
| 864 extraVisibleCell = candidateCell; | |
| 865 break; | |
| 866 } | |
| 867 } | |
| 868 // Select the extra visible wifi. | |
| 869 if (visibleWifis != null) { | |
| 870 for (VisibleWifi candidateWifi : visibleWifis) { | |
| 871 if (shouldExcludeVisibleWifi(candidateWifi)) { | |
| 872 // Do not include this candidate wifi. | |
| 873 continue; | |
| 874 } | |
| 875 if (ApiCompatibilityUtils.objectEquals(connectedWifi, candidateW ifi)) { | |
| 876 // Replace the connected, since the candidate will have leve l. This is because | |
| 877 // the android APIs exposing connected WIFI do not expose le vel, while the ones | |
| 878 // exposing visible wifis expose level. | |
| 879 connectedWifi = candidateWifi; | |
| 880 // Do not include this candidate wifi, since its already the connected one. | |
| 881 continue; | |
| 882 } | |
| 883 // Keep the one with stronger level (since it's negative, this i s the smaller value) | |
| 884 if (extraVisibleWifi == null || extraVisibleWifi.level() > candi dateWifi.level()) { | |
| 885 extraVisibleWifi = candidateWifi; | |
| 886 } | |
| 887 } | |
| 888 } | |
| 889 | |
| 890 if (connectedCell == null && connectedWifi == null && extraVisibleCell = = null | |
| 891 && extraVisibleWifi == null) { | |
| 892 return null; | |
| 893 } | |
| 894 | |
| 895 return VisibleNetworks.create(connectedWifi, connectedCell, | |
| 896 extraVisibleWifi != null ? new HashSet<>(Arrays.asList(extraVisi bleWifi)) : null, | |
|
Ted C
2017/05/18 14:43:03
I would use newHashSet here
https://cs.chromium.o
lbargu
2017/05/18 15:14:09
Done.
| |
| 897 extraVisibleCell != null ? new HashSet<>(Arrays.asList(extraVisi bleCell)) : null); | |
| 898 } | |
| 899 | |
| 900 /** | |
| 901 * Returns whether the provided {@link VisibleWifi} should be excluded. This can happen if the | |
| 902 * network is opted out (ssid contains "_nomap" or "_optout"). | |
| 903 */ | |
| 904 private static boolean shouldExcludeVisibleWifi(@Nullable VisibleWifi visibl eWifi) { | |
| 905 if (visibleWifi == null) { | |
| 906 return true; | |
| 907 } | |
| 908 String ssid = visibleWifi.ssid(); | |
| 909 String bssid = visibleWifi.bssid(); | |
| 910 if (ssid == null) { | |
| 911 // No ssid, so the networks is not opted out and should not be exclu ded. | |
| 912 return false; | |
| 913 } | |
| 914 // Optimization to avoid costly toLowerCase() in most cases. | |
| 915 if (ssid.indexOf('_') < 0) { | |
| 916 // No "_nomap | |
| 917 return false; | |
| 918 } | |
| 919 String ssidLowerCase = ssid.toLowerCase(Locale.ENGLISH); | |
| 920 return ssidLowerCase.contains(SSID_NOMAP) || ssidLowerCase.contains(SSID _OPTOUT); | |
| 921 } | |
| 678 } | 922 } |
| OLD | NEW |