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