1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.core.location; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY; 20 21 import static java.lang.Math.min; 22 23 import android.annotation.SuppressLint; 24 import android.location.LocationRequest; 25 import android.os.Build.VERSION; 26 27 import androidx.annotation.FloatRange; 28 import androidx.annotation.IntDef; 29 import androidx.annotation.IntRange; 30 import androidx.annotation.RequiresApi; 31 import androidx.annotation.RestrictTo; 32 import androidx.core.util.Preconditions; 33 import androidx.core.util.TimeUtils; 34 35 import org.jspecify.annotations.NonNull; 36 import org.jspecify.annotations.Nullable; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.lang.reflect.InvocationTargetException; 41 import java.lang.reflect.Method; 42 43 /** 44 * Compatibility version of {@link LocationRequest}. 45 */ 46 public final class LocationRequestCompat { 47 48 /** 49 * Represents a passive only request. Such a request will not trigger any active locations or 50 * power usage itself, but may receive locations generated in response to other requests. 51 * 52 * @see LocationRequestCompat#getIntervalMillis() 53 */ 54 public static final long PASSIVE_INTERVAL = LocationRequest.PASSIVE_INTERVAL; 55 56 @RestrictTo(LIBRARY) 57 @Retention(RetentionPolicy.SOURCE) 58 @IntDef({QUALITY_LOW_POWER, QUALITY_BALANCED_POWER_ACCURACY, QUALITY_HIGH_ACCURACY}) 59 public @interface Quality { 60 } 61 62 /** 63 * A quality constant indicating a location provider may choose to satisfy this request by 64 * providing very accurate locations at the expense of potentially increased power usage. Each 65 * location provider may interpret this field differently, but as an example, the network 66 * provider may choose to return only wifi based locations rather than cell based locations in 67 * order to have greater accuracy when this flag is present. 68 */ 69 public static final int QUALITY_HIGH_ACCURACY = LocationRequest.QUALITY_HIGH_ACCURACY; 70 71 /** 72 * A quality constant indicating a location provider may choose to satisfy this request by 73 * equally balancing power and accuracy constraints. Each location provider may interpret this 74 * field differently, but location providers will generally use their default behavior when this 75 * flag is present. 76 */ 77 public static final int QUALITY_BALANCED_POWER_ACCURACY = 78 LocationRequest.QUALITY_BALANCED_POWER_ACCURACY; 79 80 /** 81 * A quality constant indicating a location provider may choose to satisfy this request by 82 * providing less accurate locations in order to save power. Each location provider may 83 * interpret this field differently, but as an example, the network provider may choose to 84 * return cell based locations rather than wifi based locations in order to save power when this 85 * flag is present. 86 */ 87 public static final int QUALITY_LOW_POWER = LocationRequest.QUALITY_LOW_POWER; 88 89 private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1; 90 91 @Quality 92 final int mQuality; 93 final long mIntervalMillis; 94 final long mMinUpdateIntervalMillis; 95 final long mDurationMillis; 96 final int mMaxUpdates; 97 final float mMinUpdateDistanceMeters; 98 final long mMaxUpdateDelayMillis; 99 LocationRequestCompat( long intervalMillis, @Quality int quality, long durationMillis, int maxUpdates, long minUpdateIntervalMillis, float minUpdateDistanceMeters, long maxUpdateDelayMillis)100 LocationRequestCompat( 101 long intervalMillis, 102 @Quality int quality, 103 long durationMillis, 104 int maxUpdates, 105 long minUpdateIntervalMillis, 106 float minUpdateDistanceMeters, 107 long maxUpdateDelayMillis) { 108 mIntervalMillis = intervalMillis; 109 mQuality = quality; 110 mMinUpdateIntervalMillis = minUpdateIntervalMillis; 111 mDurationMillis = durationMillis; 112 mMaxUpdates = maxUpdates; 113 mMinUpdateDistanceMeters = minUpdateDistanceMeters; 114 mMaxUpdateDelayMillis = maxUpdateDelayMillis; 115 } 116 117 /** 118 * Returns the quality hint for this location request. The quality hint informs the provider how 119 * it should attempt to manage any accuracy vs power tradeoffs while attempting to satisfy this 120 * location request. 121 */ getQuality()122 public @Quality int getQuality() { 123 return mQuality; 124 } 125 126 /** 127 * Returns the desired interval of location updates, or {@link #PASSIVE_INTERVAL} if this is a 128 * passive, no power request. A passive request will not actively generate location updates 129 * (and thus will not be power blamed for location), but may receive location updates generated 130 * as a result of other location requests. A passive request must always have an explicit 131 * minimum update interval set. 132 * 133 * <p>Locations may be available at a faster interval than specified here, see 134 * {@link #getMinUpdateIntervalMillis()} for the behavior in that case. 135 */ getIntervalMillis()136 public @IntRange(from = 0) long getIntervalMillis() { 137 return mIntervalMillis; 138 } 139 140 /** 141 * Returns the minimum update interval. If location updates are available faster than the 142 * request interval then locations will only be updated if the minimum update interval has 143 * expired since the last location update. 144 * 145 * <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the 146 * minimum update interval, so you need not worry about updates blocked simply because they 147 * arrived a fraction of a second earlier than expected. 148 * 149 * @return the minimum update interval 150 */ getMinUpdateIntervalMillis()151 public @IntRange(from = 0) long getMinUpdateIntervalMillis() { 152 if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) { 153 return mIntervalMillis; 154 } else { 155 return mMinUpdateIntervalMillis; 156 } 157 } 158 159 /** 160 * Returns the duration for which location will be provided before the request is automatically 161 * removed. A duration of <code>Long.MAX_VALUE</code> represents an unlimited duration. 162 * 163 * @return the duration for which location will be provided 164 */ getDurationMillis()165 public @IntRange(from = 1) long getDurationMillis() { 166 return mDurationMillis; 167 } 168 169 /** 170 * Returns the maximum number of location updates for this request before the request is 171 * automatically removed. A max updates value of <code>Integer.MAX_VALUE</code> represents an 172 * unlimited number of updates. 173 */ getMaxUpdates()174 public @IntRange(from = 1, to = Integer.MAX_VALUE) int getMaxUpdates() { 175 return mMaxUpdates; 176 } 177 178 /** 179 * Returns the minimum distance between location updates. If a potential location update is 180 * closer to the last location update than the minimum update distance, then the potential 181 * location update will not occur. A value of 0 meters implies that no location update will ever 182 * be rejected due to failing this constraint. 183 * 184 * @return the minimum distance between location updates 185 */ getMinUpdateDistanceMeters()186 public @FloatRange(from = 0, to = Float.MAX_VALUE) float getMinUpdateDistanceMeters() { 187 return mMinUpdateDistanceMeters; 188 } 189 190 /** 191 * Returns the maximum time any location update may be delayed, and thus grouped with following 192 * updates to enable location batching. If the maximum update delay is equal to or greater than 193 * twice the interval, then location providers may provide batched results. The maximum batch 194 * size is the maximum update delay divided by the interval. Not all devices or location 195 * providers support batching, and use of this parameter does not guarantee that the client will 196 * see batched results, or that batched results will always be of the maximum size. 197 * 198 * When available, batching can provide substantial power savings to the device, and clients are 199 * encouraged to take advantage where appropriate for the use case. 200 * 201 * @return the maximum time by which a location update may be delayed 202 * @see LocationListenerCompat#onLocationChanged(java.util.List) 203 */ getMaxUpdateDelayMillis()204 public @IntRange(from = 0) long getMaxUpdateDelayMillis() { 205 return mMaxUpdateDelayMillis; 206 } 207 208 /** 209 * Converts an instance to an equivalent {@link LocationRequest}. 210 * 211 * @return platform class object 212 * @see LocationRequest 213 */ 214 @RequiresApi(31) toLocationRequest()215 public @NonNull LocationRequest toLocationRequest() { 216 return Api31Impl.toLocationRequest(this); 217 } 218 219 /** 220 * Converts an instance to an equivalent {@link LocationRequest}, with the provider field of 221 * the resulting LocationRequest set to the provider argument provided to this method. 222 * 223 * <p>May return null on some SDKs if various reflective operations fail. This should only 224 * occur on non-standard Android devices, and thus should be rare. 225 * 226 * @return platform class object 227 * @see LocationRequest 228 */ 229 @SuppressLint("NewApi") toLocationRequest(@onNull String provider)230 public @Nullable LocationRequest toLocationRequest(@NonNull String provider) { 231 if (VERSION.SDK_INT >= 31) { 232 return toLocationRequest(); 233 } else { 234 // This cast will cause a VFY failure on SDK < 19, but we're stuck with it. 235 return (LocationRequest) Api19Impl.toLocationRequest(this, provider); 236 } 237 } 238 239 @Override equals(Object o)240 public boolean equals(Object o) { 241 if (this == o) { 242 return true; 243 } 244 if (!(o instanceof LocationRequestCompat)) { 245 return false; 246 } 247 248 LocationRequestCompat that = (LocationRequestCompat) o; 249 return mQuality == that.mQuality && mIntervalMillis == that.mIntervalMillis 250 && mMinUpdateIntervalMillis == that.mMinUpdateIntervalMillis 251 && mDurationMillis == that.mDurationMillis && mMaxUpdates == that.mMaxUpdates 252 && Float.compare(that.mMinUpdateDistanceMeters, mMinUpdateDistanceMeters) == 0 253 && mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis; 254 } 255 256 @Override hashCode()257 public int hashCode() { 258 int result = mQuality; 259 result = 31 * result + (int) (mIntervalMillis ^ (mIntervalMillis >>> 32)); 260 result = 31 * result + (int) (mMinUpdateIntervalMillis ^ (mMinUpdateIntervalMillis >>> 32)); 261 return result; 262 } 263 264 @Override toString()265 public @NonNull String toString() { 266 StringBuilder s = new StringBuilder(); 267 s.append("Request["); 268 if (mIntervalMillis != PASSIVE_INTERVAL) { 269 s.append("@"); 270 TimeUtils.formatDuration(mIntervalMillis, s); 271 272 switch (mQuality) { 273 case QUALITY_HIGH_ACCURACY: 274 s.append(" HIGH_ACCURACY"); 275 break; 276 case QUALITY_BALANCED_POWER_ACCURACY: 277 s.append(" BALANCED"); 278 break; 279 case QUALITY_LOW_POWER: 280 s.append(" LOW_POWER"); 281 break; 282 } 283 } else { 284 s.append("PASSIVE"); 285 } 286 if (mDurationMillis != Long.MAX_VALUE) { 287 s.append(", duration="); 288 TimeUtils.formatDuration(mDurationMillis, s); 289 } 290 if (mMaxUpdates != Integer.MAX_VALUE) { 291 s.append(", maxUpdates=").append(mMaxUpdates); 292 } 293 if (mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL 294 && mMinUpdateIntervalMillis < mIntervalMillis) { 295 s.append(", minUpdateInterval="); 296 TimeUtils.formatDuration(mMinUpdateIntervalMillis, s); 297 } 298 if (mMinUpdateDistanceMeters > 0.0) { 299 s.append(", minUpdateDistance=").append(mMinUpdateDistanceMeters); 300 } 301 if (mMaxUpdateDelayMillis / 2 > mIntervalMillis) { 302 s.append(", maxUpdateDelay="); 303 TimeUtils.formatDuration(mMaxUpdateDelayMillis, s); 304 } 305 s.append(']'); 306 return s.toString(); 307 } 308 309 /** 310 * A builder class for {@link LocationRequestCompat}. 311 */ 312 public static final class Builder { 313 314 private long mIntervalMillis; 315 private @Quality int mQuality; 316 private long mDurationMillis; 317 private int mMaxUpdates; 318 private long mMinUpdateIntervalMillis; 319 private float mMinUpdateDistanceMeters; 320 private long mMaxUpdateDelayMillis; 321 322 /** 323 * Creates a new Builder with the given interval. See {@link #setIntervalMillis(long)} for 324 * more information on the interval. Note that the defaults for various Builder parameters 325 * may be different from the defaults for the framework {@link LocationRequest}. 326 */ Builder(long intervalMillis)327 public Builder(long intervalMillis) { 328 // gives us a range check 329 setIntervalMillis(intervalMillis); 330 331 mQuality = QUALITY_BALANCED_POWER_ACCURACY; 332 mDurationMillis = Long.MAX_VALUE; 333 mMaxUpdates = Integer.MAX_VALUE; 334 mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL; 335 mMinUpdateDistanceMeters = 0; 336 mMaxUpdateDelayMillis = 0; 337 } 338 339 /** 340 * Creates a new Builder with all parameters copied from the given location request. 341 */ Builder(@onNull LocationRequestCompat locationRequest)342 public Builder(@NonNull LocationRequestCompat locationRequest) { 343 mIntervalMillis = locationRequest.mIntervalMillis; 344 mQuality = locationRequest.mQuality; 345 mDurationMillis = locationRequest.mDurationMillis; 346 mMaxUpdates = locationRequest.mMaxUpdates; 347 mMinUpdateIntervalMillis = locationRequest.mMinUpdateIntervalMillis; 348 mMinUpdateDistanceMeters = locationRequest.mMinUpdateDistanceMeters; 349 mMaxUpdateDelayMillis = locationRequest.mMaxUpdateDelayMillis; 350 } 351 352 /** 353 * Sets the request interval. The request interval may be set to {@link #PASSIVE_INTERVAL} 354 * which indicates this request will not actively generate location updates (and thus will 355 * not be power blamed for location), but may receive location updates generated as a result 356 * of other location requests. A passive request must always have an explicit minimum 357 * update interval set. 358 * 359 * <p>Locations may be available at a faster interval than specified here, see 360 * {@link #setMinUpdateIntervalMillis(long)} for the behavior in that case. 361 * 362 * <p class="note"><strong>Note:</strong> On platforms below Android 12, using the 363 * {@link #PASSIVE_INTERVAL} will not result in a truly passive request, but a request with 364 * an extremely long interval. In most cases, this is effectively the same as a passive 365 * request, but this may occasionally result in an initial location calculation for which 366 * the client will be blamed. 367 */ setIntervalMillis(@ntRangefrom = 0) long intervalMillis)368 public @NonNull Builder setIntervalMillis(@IntRange(from = 0) long intervalMillis) { 369 mIntervalMillis = Preconditions.checkArgumentInRange(intervalMillis, 0, Long 370 .MAX_VALUE, 371 "intervalMillis"); 372 return this; 373 } 374 375 /** 376 * Sets the request quality. The quality is a hint to providers on how they should weigh 377 * power vs accuracy tradeoffs. High accuracy locations may cost more power to produce, and 378 * lower accuracy locations may cost less power to produce. Defaults to 379 * {@link #QUALITY_BALANCED_POWER_ACCURACY}. 380 */ setQuality(@uality int quality)381 public @NonNull Builder setQuality(@Quality int quality) { 382 Preconditions.checkArgument( 383 quality == QUALITY_LOW_POWER || quality == QUALITY_BALANCED_POWER_ACCURACY 384 || quality == QUALITY_HIGH_ACCURACY, 385 "quality must be a defined QUALITY constant, not %d", quality); 386 mQuality = quality; 387 return this; 388 } 389 390 /** 391 * Sets the duration this request will continue before being automatically removed. Defaults 392 * to <code>Long.MAX_VALUE</code>, which represents an unlimited duration. 393 * 394 * <p class="note"><strong>Note:</strong> This parameter will be ignored on platforms below 395 * Android Kitkat, and the request will not be removed after the duration expires. 396 */ setDurationMillis(@ntRangefrom = 1) long durationMillis)397 public @NonNull Builder setDurationMillis(@IntRange(from = 1) long durationMillis) { 398 mDurationMillis = Preconditions.checkArgumentInRange(durationMillis, 1, Long 399 .MAX_VALUE, 400 "durationMillis"); 401 return this; 402 } 403 404 /** 405 * Sets the maximum number of location updates for this request before this request is 406 * automatically removed. Defaults to <code>Integer.MAX_VALUE</code>, which represents an 407 * unlimited number of updates. 408 */ setMaxUpdates( @ntRangefrom = 1, to = Integer.MAX_VALUE) int maxUpdates)409 public @NonNull Builder setMaxUpdates( 410 @IntRange(from = 1, to = Integer.MAX_VALUE) int maxUpdates) { 411 mMaxUpdates = Preconditions.checkArgumentInRange(maxUpdates, 1, Integer.MAX_VALUE, 412 "maxUpdates"); 413 return this; 414 } 415 416 /** 417 * Sets an explicit minimum update interval. If location updates are available faster than 418 * the request interval then an update will only occur if the minimum update interval has 419 * expired since the last location update. Defaults to no explicit minimum update interval 420 * set, which means the minimum update interval is the same as the interval. 421 * 422 * <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the 423 * minimum update interval, so you need not worry about updates blocked simply because they 424 * arrived a fraction of a second earlier than expected. 425 * 426 * <p class="note"><strong>Note:</strong> When {@link #build()} is invoked, the minimum of 427 * the interval and the minimum update interval will be used as the minimum update interval 428 * of the built request. 429 */ setMinUpdateIntervalMillis( @ntRangefrom = 0) long minUpdateIntervalMillis)430 public @NonNull Builder setMinUpdateIntervalMillis( 431 @IntRange(from = 0) long minUpdateIntervalMillis) { 432 mMinUpdateIntervalMillis = Preconditions.checkArgumentInRange(minUpdateIntervalMillis, 433 0, Long.MAX_VALUE, "minUpdateIntervalMillis"); 434 return this; 435 } 436 437 /** 438 * Clears an explicitly set minimum update interval and reverts to an implicit minimum 439 * update interval (ie, the minimum update interval is the same value as the interval). 440 */ clearMinUpdateIntervalMillis()441 public @NonNull Builder clearMinUpdateIntervalMillis() { 442 mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL; 443 return this; 444 } 445 446 /** 447 * Sets the minimum update distance between location updates. If a potential location 448 * update is closer to the last location update than the minimum update distance, then 449 * the potential location update will not occur. Defaults to 0, which represents no minimum 450 * update distance. 451 */ setMinUpdateDistanceMeters( @loatRangefrom = 0, to = Float.MAX_VALUE) float minUpdateDistanceMeters)452 public @NonNull Builder setMinUpdateDistanceMeters( 453 @FloatRange(from = 0, to = Float.MAX_VALUE) float minUpdateDistanceMeters) { 454 mMinUpdateDistanceMeters = minUpdateDistanceMeters; 455 mMinUpdateDistanceMeters = Preconditions.checkArgumentInRange(minUpdateDistanceMeters, 456 0, Float.MAX_VALUE, "minUpdateDistanceMeters"); 457 return this; 458 } 459 460 /** 461 * Sets the maximum time any location update may be delayed, and thus grouped with following 462 * updates to enable location batching. If the maximum update delay is equal to or greater 463 * than twice the interval, then location providers may provide batched results. Defaults to 464 * 0, which represents no batching allowed. 465 */ setMaxUpdateDelayMillis( @ntRangefrom = 0) long maxUpdateDelayMillis)466 public @NonNull Builder setMaxUpdateDelayMillis( 467 @IntRange(from = 0) long maxUpdateDelayMillis) { 468 mMaxUpdateDelayMillis = maxUpdateDelayMillis; 469 mMaxUpdateDelayMillis = Preconditions.checkArgumentInRange(maxUpdateDelayMillis, 0, 470 Long.MAX_VALUE, "maxUpdateDelayMillis"); 471 return this; 472 } 473 474 /** 475 * Builds a location request from this builder. If an explicit minimum update interval is 476 * set, the minimum update interval of the location request will be the minimum of the 477 * interval and minimum update interval. 478 * 479 * <p>If building a passive request then you must have set an explicit minimum update 480 * interval. 481 */ build()482 public @NonNull LocationRequestCompat build() { 483 Preconditions.checkState(mIntervalMillis != PASSIVE_INTERVAL 484 || mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL, 485 "passive location requests must have an explicit minimum update interval"); 486 487 return new LocationRequestCompat( 488 mIntervalMillis, 489 mQuality, 490 mDurationMillis, 491 mMaxUpdates, 492 min(mMinUpdateIntervalMillis, mIntervalMillis), 493 mMinUpdateDistanceMeters, 494 mMaxUpdateDelayMillis); 495 } 496 } 497 498 @RequiresApi(31) 499 private static class Api31Impl { Api31Impl()500 private Api31Impl() { 501 // This class is not instantiable. 502 } 503 toLocationRequest(LocationRequestCompat obj)504 public static LocationRequest toLocationRequest(LocationRequestCompat obj) { 505 return new LocationRequest.Builder(obj.getIntervalMillis()) 506 .setQuality(obj.getQuality()) 507 .setMinUpdateIntervalMillis(obj.getMinUpdateIntervalMillis()) 508 .setDurationMillis(obj.getDurationMillis()) 509 .setMaxUpdates(obj.getMaxUpdates()) 510 .setMinUpdateDistanceMeters(obj.getMinUpdateDistanceMeters()) 511 .setMaxUpdateDelayMillis(obj.getMaxUpdateDelayMillis()) 512 .build(); 513 } 514 } 515 516 private static class Api19Impl { 517 private static Class<?> sLocationRequestClass; 518 private static Method sCreateFromDeprecatedProviderMethod; 519 private static Method sSetQualityMethod; 520 private static Method sSetFastestIntervalMethod; 521 private static Method sSetNumUpdatesMethod; 522 private static Method sSetExpireInMethod; 523 Api19Impl()524 private Api19Impl() { 525 // This class is not instantiable. 526 } 527 528 @SuppressLint("BanUncheckedReflection") toLocationRequest(LocationRequestCompat obj, String provider)529 public static Object toLocationRequest(LocationRequestCompat obj, String provider) { 530 // Satisfy reflection lint check 531 try { 532 if (sLocationRequestClass == null) { 533 sLocationRequestClass = Class.forName("android.location.LocationRequest"); 534 } 535 if (sCreateFromDeprecatedProviderMethod == null) { 536 sCreateFromDeprecatedProviderMethod = 537 sLocationRequestClass.getDeclaredMethod( 538 "createFromDeprecatedProvider", String.class, long.class, 539 float.class, 540 boolean.class); 541 sCreateFromDeprecatedProviderMethod.setAccessible(true); 542 } 543 544 Object request = sCreateFromDeprecatedProviderMethod.invoke(null, 545 provider, 546 obj.getIntervalMillis(), 547 obj.getMinUpdateDistanceMeters(), false); 548 if (request == null) { 549 return null; 550 } 551 552 if (sSetQualityMethod == null) { 553 sSetQualityMethod = sLocationRequestClass.getDeclaredMethod( 554 "setQuality", int.class); 555 sSetQualityMethod.setAccessible(true); 556 } 557 sSetQualityMethod.invoke(request, obj.getQuality()); 558 559 if (sSetFastestIntervalMethod == null) { 560 sSetFastestIntervalMethod = sLocationRequestClass.getDeclaredMethod( 561 "setFastestInterval", long.class); 562 sSetFastestIntervalMethod.setAccessible(true); 563 } 564 565 sSetFastestIntervalMethod.invoke(request, obj.getMinUpdateIntervalMillis()); 566 567 if (obj.getMaxUpdates() < Integer.MAX_VALUE) { 568 if (sSetNumUpdatesMethod == null) { 569 sSetNumUpdatesMethod = sLocationRequestClass.getDeclaredMethod( 570 "setNumUpdates", int.class); 571 sSetNumUpdatesMethod.setAccessible(true); 572 } 573 574 sSetNumUpdatesMethod.invoke(request, obj.getMaxUpdates()); 575 } 576 577 if (obj.getDurationMillis() < Long.MAX_VALUE) { 578 if (sSetExpireInMethod == null) { 579 sSetExpireInMethod = sLocationRequestClass.getDeclaredMethod( 580 "setExpireIn", long.class); 581 sSetExpireInMethod.setAccessible(true); 582 } 583 584 sSetExpireInMethod.invoke(request, obj.getDurationMillis()); 585 } 586 587 return request; 588 } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException 589 | ClassNotFoundException e) { 590 // Ignore 591 } 592 return null; 593 } 594 } 595 } 596