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 |