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.CollectionUtil; |
22 import org.chromium.base.ContextUtils; | 23 import org.chromium.base.ContextUtils; |
23 import org.chromium.base.Log; | 24 import org.chromium.base.Log; |
| 25 import org.chromium.base.VisibleForTesting; |
24 import org.chromium.base.annotations.CalledByNative; | 26 import org.chromium.base.annotations.CalledByNative; |
25 import org.chromium.base.annotations.SuppressFBWarnings; | 27 import org.chromium.base.annotations.SuppressFBWarnings; |
26 import org.chromium.base.metrics.RecordHistogram; | 28 import org.chromium.base.metrics.RecordHistogram; |
27 import org.chromium.chrome.browser.ChromeFeatureList; | 29 import org.chromium.chrome.browser.ChromeFeatureList; |
28 import org.chromium.chrome.browser.UrlConstants; | 30 import org.chromium.chrome.browser.UrlConstants; |
| 31 import org.chromium.chrome.browser.omnibox.geo.VisibleNetworks.VisibleCell; |
| 32 import org.chromium.chrome.browser.omnibox.geo.VisibleNetworks.VisibleWifi; |
29 import org.chromium.chrome.browser.preferences.website.ContentSetting; | 33 import org.chromium.chrome.browser.preferences.website.ContentSetting; |
30 import org.chromium.chrome.browser.preferences.website.GeolocationInfo; | 34 import org.chromium.chrome.browser.preferences.website.GeolocationInfo; |
31 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge; | 35 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge; |
32 import org.chromium.chrome.browser.tab.Tab; | 36 import org.chromium.chrome.browser.tab.Tab; |
33 import org.chromium.chrome.browser.util.UrlUtilities; | 37 import org.chromium.chrome.browser.util.UrlUtilities; |
34 | 38 |
35 import java.lang.annotation.Retention; | 39 import java.lang.annotation.Retention; |
36 import java.lang.annotation.RetentionPolicy; | 40 import java.lang.annotation.RetentionPolicy; |
37 import java.util.Locale; | 41 import java.util.Locale; |
| 42 import java.util.Set; |
38 import java.util.concurrent.TimeUnit; | 43 import java.util.concurrent.TimeUnit; |
39 | 44 |
| 45 import javax.annotation.Nullable; |
| 46 |
40 /** | 47 /** |
41 * Provides methods for building the X-Geo HTTP header, which provides device lo
cation to a server | 48 * Provides methods for building the X-Geo HTTP header, which provides device lo
cation to a server |
42 * when making an HTTP request. | 49 * when making an HTTP request. |
43 * | 50 * |
44 * X-Geo header spec: https://goto.google.com/xgeospec. | 51 * X-Geo header spec: https://goto.google.com/xgeospec. |
45 */ | 52 */ |
46 public class GeolocationHeader { | 53 public class GeolocationHeader { |
47 private static final String TAG = "GeolocationHeader"; | 54 private static final String TAG = "GeolocationHeader"; |
48 | 55 |
49 // Values for the histogram Geolocation.HeaderSentOrNot. Values 1, 5, 6, and
7 are defined in | 56 // 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, | 148 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, | 149 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, | 150 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, | 151 UMA_PERM_MASTER_OFF_APP_PROMPT_DOMAIN_BLOCKED, |
145 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_YES, | 152 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_YES, |
146 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_PROMPT, | 153 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_PROMPT, |
147 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_BLOCKED, UMA_PERM_UNSUITABLE_
URL, | 154 UMA_PERM_MASTER_OFF_APP_BLOCKED_DOMAIN_BLOCKED, UMA_PERM_UNSUITABLE_
URL, |
148 UMA_PERM_NOT_HTTPS}) | 155 UMA_PERM_NOT_HTTPS}) |
149 public @interface UmaPermission {} | 156 public @interface UmaPermission {} |
150 | 157 |
151 private static final int LOCATION_SOURCE_HIGH_ACCURACY = 0; | 158 @VisibleForTesting |
152 private static final int LOCATION_SOURCE_BATTERY_SAVING = 1; | 159 static final int LOCATION_SOURCE_HIGH_ACCURACY = 0; |
153 private static final int LOCATION_SOURCE_GPS_ONLY = 2; | 160 @VisibleForTesting |
154 private static final int LOCATION_SOURCE_MASTER_OFF = 3; | 161 static final int LOCATION_SOURCE_BATTERY_SAVING = 1; |
| 162 @VisibleForTesting |
| 163 static final int LOCATION_SOURCE_GPS_ONLY = 2; |
| 164 @VisibleForTesting |
| 165 static final int LOCATION_SOURCE_MASTER_OFF = 3; |
155 @Retention(RetentionPolicy.SOURCE) | 166 @Retention(RetentionPolicy.SOURCE) |
156 @IntDef({LOCATION_SOURCE_HIGH_ACCURACY, LOCATION_SOURCE_BATTERY_SAVING, | 167 @IntDef({LOCATION_SOURCE_HIGH_ACCURACY, LOCATION_SOURCE_BATTERY_SAVING, |
157 LOCATION_SOURCE_GPS_ONLY, LOCATION_SOURCE_MASTER_OFF}) | 168 LOCATION_SOURCE_GPS_ONLY, LOCATION_SOURCE_MASTER_OFF}) |
158 private @interface LocationSource {} | 169 private @interface LocationSource {} |
159 | 170 |
160 private static final int PERMISSION_GRANTED = 0; | 171 private static final int PERMISSION_GRANTED = 0; |
161 private static final int PERMISSION_PROMPT = 1; | 172 private static final int PERMISSION_PROMPT = 1; |
162 private static final int PERMISSION_BLOCKED = 2; | 173 private static final int PERMISSION_BLOCKED = 2; |
163 @Retention(RetentionPolicy.SOURCE) | 174 @Retention(RetentionPolicy.SOURCE) |
164 @IntDef({PERMISSION_GRANTED, PERMISSION_PROMPT, PERMISSION_BLOCKED}) | 175 @IntDef({PERMISSION_GRANTED, PERMISSION_PROMPT, PERMISSION_BLOCKED}) |
(...skipping 14 matching lines...) Expand all Loading... |
179 @IntDef({HEADER_ENABLED, INCOGNITO, UNSUITABLE_URL, NOT_HTTPS, LOCATION_PERM
ISSION_BLOCKED}) | 190 @IntDef({HEADER_ENABLED, INCOGNITO, UNSUITABLE_URL, NOT_HTTPS, LOCATION_PERM
ISSION_BLOCKED}) |
180 private @interface HeaderState {} | 191 private @interface HeaderState {} |
181 | 192 |
182 /** The maximum age in milliseconds of a location that we'll send in an X-Ge
o header. */ | 193 /** The maximum age in milliseconds of a location that we'll send in an X-Ge
o header. */ |
183 private static final int MAX_LOCATION_AGE = 24 * 60 * 60 * 1000; // 24 hour
s | 194 private static final int MAX_LOCATION_AGE = 24 * 60 * 60 * 1000; // 24 hour
s |
184 | 195 |
185 /** The maximum age in milliseconds of a location before we'll request a ref
resh. */ | 196 /** 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 | 197 private static final int REFRESH_LOCATION_AGE = 5 * 60 * 1000; // 5 minutes |
187 | 198 |
188 /** The X-Geo header prefix, preceding any location descriptors */ | 199 /** The X-Geo header prefix, preceding any location descriptors */ |
189 private static final String XGEO_HEADER_PREFIX = "X-Geo: "; | 200 private static final String XGEO_HEADER_PREFIX = "X-Geo:"; |
| 201 |
| 202 /** |
| 203 * The location descriptor separator used in the X-Geo header to separate en
coding prefix, and |
| 204 * encoded descriptors |
| 205 */ |
| 206 private static final String LOCATION_SEPARATOR = " "; |
190 | 207 |
191 /** The location descriptor prefix used in the X-Geo header to specify a pro
to wire encoding */ | 208 /** 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 "; | 209 private static final String LOCATION_PROTO_PREFIX = "w"; |
193 | 210 |
194 /** The location descriptor prefix used in the X-Geo header to specify an AS
CII encoding */ | 211 /** 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 "; | 212 private static final String LOCATION_ASCII_PREFIX = "a"; |
196 | 213 |
197 /** The time of the first location refresh. Contains Long.MAX_VALUE if not s
et. */ | 214 /** The time of the first location refresh. Contains Long.MAX_VALUE if not s
et. */ |
198 private static long sFirstLocationTime = Long.MAX_VALUE; | 215 private static long sFirstLocationTime = Long.MAX_VALUE; |
199 | 216 |
| 217 /** Present in WiFi SSID that should not be mapped */ |
| 218 private static final String SSID_NOMAP = "_nomap"; |
| 219 |
| 220 /** Present in WiFi SSID that opted out */ |
| 221 private static final String SSID_OPTOUT = "_optout"; |
| 222 |
| 223 private static int sLocationSourceForTesting; |
| 224 private static boolean sUseLocationSourceForTesting; |
| 225 |
| 226 private static boolean sAppPermissionGrantedForTesting; |
| 227 private static boolean sUseAppPermissionGrantedForTesting; |
| 228 |
200 /** | 229 /** |
201 * Requests a location refresh so that a valid location will be available fo
r constructing | 230 * 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). | 231 * an X-Geo header in the near future (i.e. within 5 minutes). |
203 */ | 232 */ |
204 public static void primeLocationForGeoHeader() { | 233 public static void primeLocationForGeoHeader() { |
205 if (!hasGeolocationPermission()) return; | 234 if (!hasGeolocationPermission()) return; |
206 | 235 |
207 if (sFirstLocationTime == Long.MAX_VALUE) { | 236 if (sFirstLocationTime == Long.MAX_VALUE) { |
208 sFirstLocationTime = SystemClock.elapsedRealtime(); | 237 sFirstLocationTime = SystemClock.elapsedRealtime(); |
209 } | 238 } |
210 GeolocationTracker.refreshLastKnownLocation( | 239 GeolocationTracker.refreshLastKnownLocation( |
211 ContextUtils.getApplicationContext(), REFRESH_LOCATION_AGE); | 240 ContextUtils.getApplicationContext(), REFRESH_LOCATION_AGE); |
| 241 |
| 242 // Only refresh visible networks if enabled. |
| 243 if (ChromeFeatureList.isEnabled(ChromeFeatureList.XGEO_VISIBLE_NETWORKS)
) { |
| 244 VisibleNetworksTracker.refreshVisibleNetworks(ContextUtils.getApplic
ationContext()); |
| 245 } |
212 } | 246 } |
213 | 247 |
214 /** | 248 /** |
215 * Returns whether the X-Geo header is allowed to be sent for the current UR
L. | 249 * Returns whether the X-Geo header is allowed to be sent for the current UR
L. |
216 * | 250 * |
217 * @param url The URL of the request with which this header will be sent. | 251 * @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. | 252 * @param isIncognito Whether the request will happen in an incognito tab. |
219 */ | 253 */ |
220 public static boolean isGeoHeaderEnabledForUrl(String url, boolean isIncogni
to) { | 254 public static boolean isGeoHeaderEnabledForUrl(String url, boolean isIncogni
to) { |
221 return geoHeaderStateForUrl(url, isIncognito, false) == HEADER_ENABLED; | 255 return geoHeaderStateForUrl(url, isIncognito, false) == HEADER_ENABLED; |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
253 * 3. The user has not disabled sharing location with this url, and | 287 * 3. The user has not disabled sharing location with this url, and |
254 * 4. There is a valid and recent location available. | 288 * 4. There is a valid and recent location available. |
255 * | 289 * |
256 * Returns null otherwise. | 290 * Returns null otherwise. |
257 * | 291 * |
258 * @param url The URL of the request with which this header will be sent. | 292 * @param url The URL of the request with which this header will be sent. |
259 * @param tab The Tab currently being accessed. | 293 * @param tab The Tab currently being accessed. |
260 * @return The X-Geo header string or null. | 294 * @return The X-Geo header string or null. |
261 */ | 295 */ |
262 public static String getGeoHeader(String url, Tab tab) { | 296 public static String getGeoHeader(String url, Tab tab) { |
| 297 // TODO(lbargu): Refactor and simplify flow. |
263 boolean isIncognito = tab.isIncognito(); | 298 boolean isIncognito = tab.isIncognito(); |
264 boolean locationAttached = true; | 299 Location locationToAttach = null; |
265 Location location = null; | 300 VisibleNetworks visibleNetworksToAttach = null; |
266 long locationAge = Long.MAX_VALUE; | 301 long locationAge = Long.MAX_VALUE; |
267 @HeaderState int headerState = geoHeaderStateForUrl(url, isIncognito, tr
ue); | 302 @HeaderState int headerState = geoHeaderStateForUrl(url, isIncognito, tr
ue); |
| 303 // XGEO_VISIBLE_NETWORKS |
| 304 // When this feature is enabled, we will send visible WiFi and Cell Acce
ss Points as part of |
| 305 // the X-GEO HTTP Header so that we can better position the client serve
r side in the case |
| 306 // where there is no lat/long or it's too old. |
268 boolean isXGeoVisibleNetworksEnabled = | 307 boolean isXGeoVisibleNetworksEnabled = |
269 ChromeFeatureList.isEnabled(ChromeFeatureList.XGEO_VISIBLE_NETWO
RKS); | 308 ChromeFeatureList.isEnabled(ChromeFeatureList.XGEO_VISIBLE_NETWO
RKS); |
270 if (headerState == HEADER_ENABLED) { | 309 if (headerState == HEADER_ENABLED) { |
271 // Only send X-Geo header if there's a fresh location available. | 310 // 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 | 311 // Use flag controlling visible network changes to decide whether GP
S location should be |
273 // included as a fallback. | 312 // included as a fallback. |
274 location = GeolocationTracker.getLastKnownLocation( | 313 // TODO(lbargu): Measure timing here and to get visible networks. |
| 314 locationToAttach = GeolocationTracker.getLastKnownLocation( |
275 ContextUtils.getApplicationContext(), isXGeoVisibleNetworksE
nabled); | 315 ContextUtils.getApplicationContext(), isXGeoVisibleNetworksE
nabled); |
276 if (location == null) { | 316 if (locationToAttach == null) { |
277 recordHistogram(UMA_LOCATION_NOT_AVAILABLE); | 317 recordHistogram(UMA_LOCATION_NOT_AVAILABLE); |
278 locationAttached = false; | |
279 } else { | 318 } else { |
280 locationAge = GeolocationTracker.getLocationAge(location); | 319 locationAge = GeolocationTracker.getLocationAge(locationToAttach
); |
281 if (locationAge > MAX_LOCATION_AGE) { | 320 if (locationAge > MAX_LOCATION_AGE) { |
| 321 // Do not attach the location |
282 recordHistogram(UMA_LOCATION_STALE); | 322 recordHistogram(UMA_LOCATION_STALE); |
283 locationAttached = false; | 323 locationToAttach = null; |
| 324 } else { |
| 325 recordHistogram(UMA_HEADER_SENT); |
284 } | 326 } |
285 } | 327 } |
286 } else { | 328 |
287 locationAttached = false; | 329 // The header state is enabled, so this means we have app permission
s, and the url is |
| 330 // allowed to receive location. Before attempting to attach visible
networks, check if |
| 331 // network-based location is enabled. |
| 332 if (isXGeoVisibleNetworksEnabled && isNetworkLocationEnabled() |
| 333 && !isLocationFresh(locationToAttach)) { |
| 334 visibleNetworksToAttach = VisibleNetworksTracker.getLastKnownVis
ibleNetworks( |
| 335 ContextUtils.getApplicationContext()); |
| 336 } |
288 } | 337 } |
289 | 338 |
290 @LocationSource int locationSource = getLocationSource(); | 339 @LocationSource int locationSource = getLocationSource(); |
291 @Permission int appPermission = getGeolocationPermission(tab); | 340 @Permission int appPermission = getGeolocationPermission(tab); |
292 @Permission int domainPermission = getDomainPermission(url, isIncognito)
; | 341 @Permission int domainPermission = getDomainPermission(url, isIncognito)
; |
293 | 342 |
294 // Record the permission state with a histogram. | 343 // Record the permission state with a histogram. |
295 recordPermissionHistogram( | 344 recordPermissionHistogram(locationSource, appPermission, domainPermissio
n, |
296 locationSource, appPermission, domainPermission, locationAttache
d, headerState); | 345 locationToAttach != null, headerState); |
297 | 346 |
298 if (locationSource != LOCATION_SOURCE_MASTER_OFF && appPermission != PER
MISSION_BLOCKED | 347 if (locationSource != LOCATION_SOURCE_MASTER_OFF && appPermission != PER
MISSION_BLOCKED |
299 && domainPermission != PERMISSION_BLOCKED && !isIncognito) { | 348 && domainPermission != PERMISSION_BLOCKED && !isIncognito) { |
300 // Record the Location Age with a histogram. | 349 // Record the Location Age with a histogram. |
301 recordLocationAgeHistogram(locationSource, locationAge); | 350 recordLocationAgeHistogram(locationSource, locationAge); |
302 long duration = sFirstLocationTime == Long.MAX_VALUE | 351 long duration = sFirstLocationTime == Long.MAX_VALUE |
303 ? 0 | 352 ? 0 |
304 : SystemClock.elapsedRealtime() - sFirstLocationTime; | 353 : SystemClock.elapsedRealtime() - sFirstLocationTime; |
305 // Record the Time Listening with a histogram. | 354 // Record the Time Listening with a histogram. |
306 recordTimeListeningHistogram(locationSource, locationAttached, durat
ion); | 355 recordTimeListeningHistogram(locationSource, locationToAttach != nul
l, duration); |
307 } | 356 } |
308 | 357 |
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 | 358 |
333 if (!isXGeoVisibleNetworksEnabled) { | 359 if (!isXGeoVisibleNetworksEnabled) { |
334 return XGEO_HEADER_PREFIX + LOCATION_ASCII_PREFIX + locationAsciiEnc
oding; | 360 String locationAsciiEncoding = encodeAsciiLocation(locationToAttach)
; |
| 361 if (locationAsciiEncoding == null) return null; |
| 362 return XGEO_HEADER_PREFIX + LOCATION_SEPARATOR + LOCATION_ASCII_PREF
IX |
| 363 + LOCATION_SEPARATOR + locationAsciiEncoding; |
335 } | 364 } |
336 | 365 |
337 // Create a LatLng for the coordinates. | 366 // Proto encoding |
338 PartnerLocationDescriptor.LatLng latlng = new PartnerLocationDescriptor.
LatLng(); | 367 String locationProtoEncoding = encodeProtoLocation(locationToAttach); |
339 latlng.latitudeE7 = latitudeE7; | 368 String visibleNetworksProtoEncoding = encodeProtoVisibleNetworks(visible
NetworksToAttach); |
340 latlng.longitudeE7 = longitudeE7; | |
341 | 369 |
342 // Populate a LocationDescriptor with the LatLng. | 370 if (locationProtoEncoding == null && visibleNetworksProtoEncoding == nul
l) return null; |
343 PartnerLocationDescriptor.LocationDescriptor locationDescriptor = | |
344 new PartnerLocationDescriptor.LocationDescriptor(); | |
345 locationDescriptor.latlng = latlng; | |
346 // Include role, producer, timestamp and radius. | |
347 locationDescriptor.role = PartnerLocationDescriptor.CURRENT_LOCATION; | |
348 locationDescriptor.producer = PartnerLocationDescriptor.DEVICE_LOCATION; | |
349 locationDescriptor.timestamp = timestamp; | |
350 locationDescriptor.radius = (float) radius; | |
351 | 371 |
352 String locationProtoEncoding = Base64.encodeToString( | 372 StringBuilder header = new StringBuilder(XGEO_HEADER_PREFIX); |
353 MessageNano.toByteArray(locationDescriptor), Base64.NO_WRAP | Ba
se64.URL_SAFE); | 373 if (locationProtoEncoding != null) { |
354 | 374 header.append(LOCATION_SEPARATOR).append(LOCATION_PROTO_PREFIX) |
355 return XGEO_HEADER_PREFIX + LOCATION_PROTO_PREFIX + locationProtoEncodin
g; | 375 .append(LOCATION_SEPARATOR).append(locationProtoEncoding); |
| 376 } |
| 377 if (visibleNetworksProtoEncoding != null) { |
| 378 header.append(LOCATION_SEPARATOR).append(LOCATION_PROTO_PREFIX) |
| 379 .append(LOCATION_SEPARATOR).append(visibleNetworksProtoEncod
ing); |
| 380 } |
| 381 return header.toString(); |
356 } | 382 } |
357 | 383 |
358 @CalledByNative | 384 @CalledByNative |
359 static boolean hasGeolocationPermission() { | 385 static boolean hasGeolocationPermission() { |
| 386 if (sUseAppPermissionGrantedForTesting) return sAppPermissionGrantedForT
esting; |
360 int pid = Process.myPid(); | 387 int pid = Process.myPid(); |
361 int uid = Process.myUid(); | 388 int uid = Process.myUid(); |
362 if (ApiCompatibilityUtils.checkPermission(ContextUtils.getApplicationCon
text(), | 389 if (ApiCompatibilityUtils.checkPermission(ContextUtils.getApplicationCon
text(), |
363 Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) | 390 Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) |
364 != PackageManager.PERMISSION_GRANTED) { | 391 != PackageManager.PERMISSION_GRANTED) { |
365 return false; | 392 return false; |
366 } | 393 } |
367 | 394 |
368 // Work around a bug in OnePlus2 devices running Lollipop, where the NET
WORK_PROVIDER | 395 // 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 | 396 // incorrectly requires FINE_LOCATION permission (it should only require
COARSE_LOCATION |
370 // permission). http://crbug.com/580733 | 397 // permission). http://crbug.com/580733 |
371 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M | 398 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M |
372 && ApiCompatibilityUtils.checkPermission(ContextUtils.getApplica
tionContext(), | 399 && ApiCompatibilityUtils.checkPermission(ContextUtils.getApplica
tionContext(), |
373 Manifest.permission.ACCESS_FINE_LOCATION, pid, uid) | 400 Manifest.permission.ACCESS_FINE_LOCATION, pid, uid) |
374 != PackageManager.PERMISSION_GRANTED) { | 401 != PackageManager.PERMISSION_GRANTED) { |
375 return false; | 402 return false; |
376 } | 403 } |
377 | 404 |
378 return true; | 405 return true; |
379 } | 406 } |
380 | 407 |
381 /** | 408 /** |
382 * Returns the app level geolocation permission. | 409 * Returns the app level geolocation permission. |
383 * This permission can be either granted, blocked or prompt. | 410 * This permission can be either granted, blocked or prompt. |
384 */ | 411 */ |
385 @Permission | 412 @Permission |
386 static int getGeolocationPermission(Tab tab) { | 413 static int getGeolocationPermission(Tab tab) { |
| 414 if (sUseAppPermissionGrantedForTesting) { |
| 415 return sAppPermissionGrantedForTesting ? PERMISSION_GRANTED : PERMIS
SION_BLOCKED; |
| 416 } |
387 if (hasGeolocationPermission()) return PERMISSION_GRANTED; | 417 if (hasGeolocationPermission()) return PERMISSION_GRANTED; |
388 return tab.getWindowAndroid().canRequestPermission( | 418 return tab.getWindowAndroid().canRequestPermission( |
389 Manifest.permission.ACCESS_COARSE_LOCATION) | 419 Manifest.permission.ACCESS_COARSE_LOCATION) |
390 ? PERMISSION_PROMPT | 420 ? PERMISSION_PROMPT |
391 : PERMISSION_BLOCKED; | 421 : PERMISSION_BLOCKED; |
392 } | 422 } |
393 | 423 |
394 /** | 424 /** |
395 * Returns true if the user has disabled sharing their location with url (e.
g. via the | 425 * Returns true if the user has disabled sharing their location with url (e.
g. via the |
396 * geolocation infobar). | 426 * geolocation infobar). |
(...skipping 12 matching lines...) Expand all Loading... |
409 /** | 439 /** |
410 * Returns the location permission for sharing their location with url (e.g.
via the | 440 * Returns the location permission for sharing their location with url (e.g.
via the |
411 * geolocation infobar). | 441 * geolocation infobar). |
412 */ | 442 */ |
413 static ContentSetting locationContentSettingForUrl(Uri uri, boolean isIncogn
ito) { | 443 static ContentSetting locationContentSettingForUrl(Uri uri, boolean isIncogn
ito) { |
414 GeolocationInfo locationSettings = new GeolocationInfo(uri.toString(), n
ull, isIncognito); | 444 GeolocationInfo locationSettings = new GeolocationInfo(uri.toString(), n
ull, isIncognito); |
415 ContentSetting locationPermission = locationSettings.getContentSetting()
; | 445 ContentSetting locationPermission = locationSettings.getContentSetting()
; |
416 return locationPermission; | 446 return locationPermission; |
417 } | 447 } |
418 | 448 |
| 449 @VisibleForTesting |
| 450 static void setLocationSourceForTesting(int locationSourceForTesting) { |
| 451 sLocationSourceForTesting = locationSourceForTesting; |
| 452 sUseLocationSourceForTesting = true; |
| 453 } |
| 454 |
| 455 @VisibleForTesting |
| 456 static void setAppPermissionGrantedForTesting(boolean appPermissionGrantedFo
rTesting) { |
| 457 sAppPermissionGrantedForTesting = appPermissionGrantedForTesting; |
| 458 sUseAppPermissionGrantedForTesting = true; |
| 459 } |
| 460 |
419 /** Records a data point for the Geolocation.HeaderSentOrNot histogram. */ | 461 /** Records a data point for the Geolocation.HeaderSentOrNot histogram. */ |
420 private static void recordHistogram(int result) { | 462 private static void recordHistogram(int result) { |
421 RecordHistogram.recordEnumeratedHistogram("Geolocation.HeaderSentOrNot",
result, UMA_MAX); | 463 RecordHistogram.recordEnumeratedHistogram("Geolocation.HeaderSentOrNot",
result, UMA_MAX); |
422 } | 464 } |
423 | 465 |
424 /** Returns the location source. */ | 466 /** Returns the location source. */ |
425 @LocationSource | 467 @LocationSource |
426 // We should replace our usage of LOCATION_PROVIDERS_ALLOWED when the min AP
I is 19. | 468 // We should replace our usage of LOCATION_PROVIDERS_ALLOWED when the min AP
I is 19. |
427 @SuppressWarnings("deprecation") | 469 @SuppressWarnings("deprecation") |
428 private static int getLocationSource() { | 470 private static int getLocationSource() { |
| 471 if (sUseLocationSourceForTesting) return sLocationSourceForTesting; |
| 472 |
429 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | 473 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
430 int locationMode; | 474 int locationMode; |
431 try { | 475 try { |
432 locationMode = Settings.Secure.getInt( | 476 locationMode = Settings.Secure.getInt( |
433 ContextUtils.getApplicationContext().getContentResolver(
), | 477 ContextUtils.getApplicationContext().getContentResolver(
), |
434 Settings.Secure.LOCATION_MODE); | 478 Settings.Secure.LOCATION_MODE); |
435 } catch (Settings.SettingNotFoundException e) { | 479 } catch (Settings.SettingNotFoundException e) { |
436 Log.e(TAG, "Error getting the LOCATION_MODE"); | 480 Log.e(TAG, "Error getting the LOCATION_MODE"); |
437 return LOCATION_SOURCE_MASTER_OFF; | 481 return LOCATION_SOURCE_MASTER_OFF; |
438 } | 482 } |
(...skipping 16 matching lines...) Expand all Loading... |
455 } else if (locationProviders.contains(LocationManager.GPS_PROVIDER))
{ | 499 } else if (locationProviders.contains(LocationManager.GPS_PROVIDER))
{ |
456 return LOCATION_SOURCE_GPS_ONLY; | 500 return LOCATION_SOURCE_GPS_ONLY; |
457 } else if (locationProviders.contains(LocationManager.NETWORK_PROVID
ER)) { | 501 } else if (locationProviders.contains(LocationManager.NETWORK_PROVID
ER)) { |
458 return LOCATION_SOURCE_BATTERY_SAVING; | 502 return LOCATION_SOURCE_BATTERY_SAVING; |
459 } else { | 503 } else { |
460 return LOCATION_SOURCE_MASTER_OFF; | 504 return LOCATION_SOURCE_MASTER_OFF; |
461 } | 505 } |
462 } | 506 } |
463 } | 507 } |
464 | 508 |
| 509 private static boolean isNetworkLocationEnabled() { |
| 510 int locationSource = getLocationSource(); |
| 511 return locationSource == LOCATION_SOURCE_HIGH_ACCURACY |
| 512 || locationSource == LOCATION_SOURCE_BATTERY_SAVING; |
| 513 } |
| 514 |
| 515 private static boolean isLocationFresh(@Nullable Location location) { |
| 516 return location != null |
| 517 && GeolocationTracker.getLocationAge(location) <= REFRESH_LOCATI
ON_AGE; |
| 518 } |
| 519 |
465 /** | 520 /** |
466 * Returns the domain permission as either granted, blocked or prompt. | 521 * Returns the domain permission as either granted, blocked or prompt. |
467 * This is based upon the location permission for sharing their location wit
h url (e.g. via the | 522 * This is based upon the location permission for sharing their location wit
h url (e.g. via the |
468 * geolocation infobar). | 523 * geolocation infobar). |
469 */ | 524 */ |
470 @Permission | 525 @Permission |
471 private static int getDomainPermission(String url, boolean isIncognito) { | 526 private static int getDomainPermission(String url, boolean isIncognito) { |
472 ContentSetting domainPermission = locationContentSettingForUrl(Uri.parse
(url), isIncognito); | 527 ContentSetting domainPermission = locationContentSettingForUrl(Uri.parse
(url), isIncognito); |
473 switch (domainPermission) { | 528 switch (domainPermission) { |
474 case ALLOW: | 529 case ALLOW: |
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
668 Log.e(TAG, "Unexpected locationSource: " + locationSource); | 723 Log.e(TAG, "Unexpected locationSource: " + locationSource); |
669 assert false : "Unexpected locationSource: " + locationSource; | 724 assert false : "Unexpected locationSource: " + locationSource; |
670 return; | 725 return; |
671 } | 726 } |
672 long durationSeconds = durationMillis / 1000; | 727 long durationSeconds = durationMillis / 1000; |
673 int duration = durationSeconds >= (long) Integer.MAX_VALUE ? Integer.MAX
_VALUE | 728 int duration = durationSeconds >= (long) Integer.MAX_VALUE ? Integer.MAX
_VALUE |
674 : (int) durat
ionSeconds; | 729 : (int) durat
ionSeconds; |
675 RecordHistogram.recordCustomCountHistogram( | 730 RecordHistogram.recordCustomCountHistogram( |
676 name, duration, 1, LOCATION_AGE_HISTOGRAM_MAX_SECONDS, 50); | 731 name, duration, 1, LOCATION_AGE_HISTOGRAM_MAX_SECONDS, 50); |
677 } | 732 } |
| 733 |
| 734 /** |
| 735 * Encodes location into ascii encoding. |
| 736 */ |
| 737 @Nullable |
| 738 @VisibleForTesting |
| 739 static String encodeAsciiLocation(@Nullable Location location) { |
| 740 if (location == null) return null; |
| 741 |
| 742 // Timestamp in microseconds since the UNIX epoch. |
| 743 long timestamp = location.getTime() * 1000; |
| 744 // Latitude times 1e7. |
| 745 int latitudeE7 = (int) (location.getLatitude() * 10000000); |
| 746 // Longitude times 1e7. |
| 747 int longitudeE7 = (int) (location.getLongitude() * 10000000); |
| 748 // Radius of 68% accuracy in mm. |
| 749 int radius = (int) (location.getAccuracy() * 1000); |
| 750 |
| 751 // Encode location using ascii protobuf format followed by base64 encodi
ng. |
| 752 // https://goto.google.com/partner_location_proto |
| 753 String locationAscii = String.format(Locale.US, |
| 754 "role:1 producer:12 timestamp:%d latlng{latitude_e7:%d longitude
_e7:%d}" |
| 755 + " radius:%d", |
| 756 timestamp, latitudeE7, longitudeE7, radius); |
| 757 return new String(Base64.encode(locationAscii.getBytes(), Base64.NO_WRAP
)); |
| 758 } |
| 759 |
| 760 /** |
| 761 * Encodes location into proto encoding. |
| 762 */ |
| 763 @Nullable |
| 764 @VisibleForTesting |
| 765 static String encodeProtoLocation(@Nullable Location location) { |
| 766 if (location == null) return null; |
| 767 |
| 768 // Timestamp in microseconds since the UNIX epoch. |
| 769 long timestamp = location.getTime() * 1000; |
| 770 // Latitude times 1e7. |
| 771 int latitudeE7 = (int) (location.getLatitude() * 10000000); |
| 772 // Longitude times 1e7. |
| 773 int longitudeE7 = (int) (location.getLongitude() * 10000000); |
| 774 // Radius of 68% accuracy in mm. |
| 775 int radius = (int) (location.getAccuracy() * 1000); |
| 776 |
| 777 // Create a LatLng for the coordinates. |
| 778 PartnerLocationDescriptor.LatLng latlng = new PartnerLocationDescriptor.
LatLng(); |
| 779 latlng.latitudeE7 = latitudeE7; |
| 780 latlng.longitudeE7 = longitudeE7; |
| 781 |
| 782 // Populate a LocationDescriptor with the LatLng. |
| 783 PartnerLocationDescriptor.LocationDescriptor locationDescriptor = |
| 784 new PartnerLocationDescriptor.LocationDescriptor(); |
| 785 locationDescriptor.latlng = latlng; |
| 786 // Include role, producer, timestamp and radius. |
| 787 locationDescriptor.role = PartnerLocationDescriptor.CURRENT_LOCATION; |
| 788 locationDescriptor.producer = PartnerLocationDescriptor.DEVICE_LOCATION; |
| 789 locationDescriptor.timestamp = timestamp; |
| 790 locationDescriptor.radius = (float) radius; |
| 791 return encodeLocationDescriptor(locationDescriptor); |
| 792 } |
| 793 |
| 794 /** |
| 795 * Encodes the given proto location descriptor into a BASE64 URL_SAFE encodi
ng. |
| 796 */ |
| 797 private static String encodeLocationDescriptor( |
| 798 PartnerLocationDescriptor.LocationDescriptor locationDescriptor) { |
| 799 return Base64.encodeToString( |
| 800 MessageNano.toByteArray(locationDescriptor), Base64.NO_WRAP | Ba
se64.URL_SAFE); |
| 801 } |
| 802 |
| 803 /** |
| 804 * Encodes visible networks in proto encoding. |
| 805 */ |
| 806 @Nullable |
| 807 @VisibleForTesting |
| 808 static String encodeProtoVisibleNetworks(@Nullable VisibleNetworks visibleNe
tworks) { |
| 809 VisibleNetworks visibleNetworksToEncode = trimVisibleNetworks(visibleNet
works); |
| 810 if (visibleNetworksToEncode == null) { |
| 811 // No data to encode. |
| 812 return null; |
| 813 } |
| 814 VisibleWifi connectedWifi = visibleNetworksToEncode.connectedWifi(); |
| 815 VisibleCell connectedCell = visibleNetworksToEncode.connectedCell(); |
| 816 Set<VisibleWifi> visibleWifis = visibleNetworksToEncode.allVisibleWifis(
); |
| 817 Set<VisibleCell> visibleCells = visibleNetworksToEncode.allVisibleCells(
); |
| 818 |
| 819 int numVisibleNetworks = (connectedWifi != null ? 1 : 0) |
| 820 + (visibleWifis != null ? visibleWifis.size() : 0) + (connectedC
ell != null ? 1 : 0) |
| 821 + (visibleCells != null ? visibleCells.size() : 0); |
| 822 if (numVisibleNetworks == 0) { |
| 823 // No data to encode. |
| 824 return null; |
| 825 } |
| 826 |
| 827 int i = 0; |
| 828 PartnerLocationDescriptor.VisibleNetwork[] protoNetworks = |
| 829 new PartnerLocationDescriptor.VisibleNetwork[numVisibleNetworks]
; |
| 830 if (connectedWifi != null) { |
| 831 protoNetworks[i++] = connectedWifi.toProto(true); |
| 832 } |
| 833 if (visibleWifis != null) { |
| 834 for (VisibleWifi visibleWifi : visibleWifis) { |
| 835 protoNetworks[i++] = visibleWifi.toProto(false); |
| 836 } |
| 837 } |
| 838 if (connectedCell != null) { |
| 839 protoNetworks[i++] = connectedCell.toProto(true); |
| 840 } |
| 841 if (visibleCells != null) { |
| 842 for (VisibleCell visibleCell : visibleCells) { |
| 843 protoNetworks[i++] = visibleCell.toProto(false); |
| 844 } |
| 845 } |
| 846 |
| 847 PartnerLocationDescriptor.LocationDescriptor locationDescriptor = |
| 848 new PartnerLocationDescriptor.LocationDescriptor(); |
| 849 locationDescriptor.role = PartnerLocationDescriptor.CURRENT_LOCATION; |
| 850 locationDescriptor.producer = PartnerLocationDescriptor.DEVICE_LOCATION; |
| 851 locationDescriptor.visibleNetwork = protoNetworks; |
| 852 |
| 853 return encodeLocationDescriptor(locationDescriptor); |
| 854 } |
| 855 |
| 856 @Nullable |
| 857 @VisibleForTesting |
| 858 static VisibleNetworks trimVisibleNetworks(@Nullable VisibleNetworks visible
Networks) { |
| 859 if (visibleNetworks == null || visibleNetworks.isEmpty()) { |
| 860 return null; |
| 861 } |
| 862 // Trim visible networks to only include a limited number of visible not
-conntected networks |
| 863 // based on flag. |
| 864 VisibleCell connectedCell = visibleNetworks.connectedCell(); |
| 865 VisibleWifi connectedWifi = visibleNetworks.connectedWifi(); |
| 866 Set<VisibleCell> visibleCells = visibleNetworks.allVisibleCells(); |
| 867 Set<VisibleWifi> visibleWifis = visibleNetworks.allVisibleWifis(); |
| 868 VisibleCell extraVisibleCell = null; |
| 869 VisibleWifi extraVisibleWifi = null; |
| 870 if (shouldExcludeVisibleWifi(connectedWifi)) { |
| 871 // Trim the connected wifi. |
| 872 connectedWifi = null; |
| 873 } |
| 874 // Select the extra visible cell. |
| 875 if (visibleCells != null) { |
| 876 for (VisibleCell candidateCell : visibleCells) { |
| 877 if (ApiCompatibilityUtils.objectEquals(connectedCell, candidateC
ell)) { |
| 878 // Do not include this candidate cell, since its already the
connected one. |
| 879 continue; |
| 880 } |
| 881 // Add it and since we only want one, stop iterating over other
cells. |
| 882 extraVisibleCell = candidateCell; |
| 883 break; |
| 884 } |
| 885 } |
| 886 // Select the extra visible wifi. |
| 887 if (visibleWifis != null) { |
| 888 for (VisibleWifi candidateWifi : visibleWifis) { |
| 889 if (shouldExcludeVisibleWifi(candidateWifi)) { |
| 890 // Do not include this candidate wifi. |
| 891 continue; |
| 892 } |
| 893 if (ApiCompatibilityUtils.objectEquals(connectedWifi, candidateW
ifi)) { |
| 894 // Replace the connected, since the candidate will have leve
l. This is because |
| 895 // the android APIs exposing connected WIFI do not expose le
vel, while the ones |
| 896 // exposing visible wifis expose level. |
| 897 connectedWifi = candidateWifi; |
| 898 // Do not include this candidate wifi, since its already the
connected one. |
| 899 continue; |
| 900 } |
| 901 // Keep the one with stronger level (since it's negative, this i
s the smaller value) |
| 902 if (extraVisibleWifi == null || extraVisibleWifi.level() > candi
dateWifi.level()) { |
| 903 extraVisibleWifi = candidateWifi; |
| 904 } |
| 905 } |
| 906 } |
| 907 |
| 908 if (connectedCell == null && connectedWifi == null && extraVisibleCell =
= null |
| 909 && extraVisibleWifi == null) { |
| 910 return null; |
| 911 } |
| 912 |
| 913 return VisibleNetworks.create(connectedWifi, connectedCell, |
| 914 extraVisibleWifi != null ? CollectionUtil.newHashSet(extraVisibl
eWifi) : null, |
| 915 extraVisibleCell != null ? CollectionUtil.newHashSet(extraVisibl
eCell) : null); |
| 916 } |
| 917 |
| 918 /** |
| 919 * Returns whether the provided {@link VisibleWifi} should be excluded. This
can happen if the |
| 920 * network is opted out (ssid contains "_nomap" or "_optout"). |
| 921 */ |
| 922 private static boolean shouldExcludeVisibleWifi(@Nullable VisibleWifi visibl
eWifi) { |
| 923 if (visibleWifi == null || visibleWifi.bssid() == null) { |
| 924 return true; |
| 925 } |
| 926 String ssid = visibleWifi.ssid(); |
| 927 if (ssid == null) { |
| 928 // No ssid, so the networks is not opted out and should not be exclu
ded. |
| 929 return false; |
| 930 } |
| 931 // Optimization to avoid costly toLowerCase() in most cases. |
| 932 if (ssid.indexOf('_') < 0) { |
| 933 // No "_nomap" or "_optout". |
| 934 return false; |
| 935 } |
| 936 String ssidLowerCase = ssid.toLowerCase(Locale.ENGLISH); |
| 937 return ssidLowerCase.contains(SSID_NOMAP) || ssidLowerCase.contains(SSID
_OPTOUT); |
| 938 } |
678 } | 939 } |
OLD | NEW |