1 /*
2  * Copyright (C) 2012 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 android.location;
18 
19 import android.annotation.SystemApi;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.os.SystemClock;
23 import android.os.WorkSource;
24 import android.util.TimeUtils;
25 
26 
27 /**
28  * A data object that contains quality of service parameters for requests
29  * to the {@link LocationManager}.
30  *
31  * <p>LocationRequest objects are used to request a quality of service
32  * for location updates from the Location Manager.
33  *
34  * <p>For example, if your application wants high accuracy location
35  * it should create a location request with {@link #setQuality} set to
36  * {@link #ACCURACY_FINE} or {@link #POWER_HIGH}, and it should set
37  * {@link #setInterval} to less than one second. This would be
38  * appropriate for mapping applications that are showing your location
39  * in real-time.
40  *
41  * <p>At the other extreme, if you want negligible power
42  * impact, but to still receive location updates when available, then use
43  * {@link #setQuality} with {@link #POWER_NONE}. With this request your
44  * application will not trigger (and therefore will not receive any
45  * power blame) any location updates, but will receive locations
46  * triggered by other applications. This would be appropriate for
47  * applications that have no firm requirement for location, but can
48  * take advantage when available.
49  *
50  * <p>In between these two extremes is a very common use-case, where
51  * applications definitely want to receive
52  * updates at a specified interval, and can receive them faster when
53  * available, but still want a low power impact. These applications
54  * should consider {@link #POWER_LOW} combined with a faster
55  * {@link #setFastestInterval} (such as 1 minute) and a slower
56  * {@link #setInterval} (such as 60 minutes). They will only be assigned
57  * power blame for the interval set by {@link #setInterval}, but can
58  * still receive locations triggered by other applications at a rate up
59  * to {@link #setFastestInterval}. This style of request is appropriate for
60  * many location aware applications, including background usage. Do be
61  * careful to also throttle {@link #setFastestInterval} if you perform
62  * heavy-weight work after receiving an update - such as using the network.
63  *
64  * <p>Activities should strongly consider removing all location
65  * request when entering the background
66  * (for example at {@link android.app.Activity#onPause}), or
67  * at least swap the request to a larger interval and lower quality.
68  * Future version of the location manager may automatically perform background
69  * throttling on behalf of applications.
70  *
71  * <p>Applications cannot specify the exact location sources that are
72  * used by Android's <em>Fusion Engine</em>. In fact, the system
73  * may have multiple location sources (providers) running and may
74  * fuse the results from several sources into a single Location object.
75  *
76  * <p>Location requests from applications with
77  * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and not
78  * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} will
79  * be automatically throttled to a slower interval, and the location
80  * object will be obfuscated to only show a coarse level of accuracy.
81  *
82  * <p>All location requests are considered hints, and you may receive
83  * locations that are more accurate, less accurate, and slower
84  * than requested.
85  *
86  * @hide
87  */
88 @SystemApi
89 public final class LocationRequest implements Parcelable {
90     /**
91      * Used with {@link #setQuality} to request the most accurate locations available.
92      *
93      * <p>This may be up to 1 meter accuracy, although this is implementation dependent.
94      */
95     public static final int ACCURACY_FINE = 100;
96 
97     /**
98      * Used with {@link #setQuality} to request "block" level accuracy.
99      *
100      * <p>Block level accuracy is considered to be about 100 meter accuracy,
101      * although this is implementation dependent. Using a coarse accuracy
102      * such as this often consumes less power.
103      */
104     public static final int ACCURACY_BLOCK = 102;
105 
106     /**
107      * Used with {@link #setQuality} to request "city" level accuracy.
108      *
109      * <p>City level accuracy is considered to be about 10km accuracy,
110      * although this is implementation dependent. Using a coarse accuracy
111      * such as this often consumes less power.
112      */
113     public static final int ACCURACY_CITY = 104;
114 
115     /**
116      * Used with {@link #setQuality} to require no direct power impact (passive locations).
117      *
118      * <p>This location request will not trigger any active location requests,
119      * but will receive locations triggered by other applications. Your application
120      * will not receive any direct power blame for location work.
121      */
122     public static final int POWER_NONE = 200;
123 
124     /**
125      * Used with {@link #setQuality} to request low power impact.
126      *
127      * <p>This location request will avoid high power location work where
128      * possible.
129      */
130     public static final int POWER_LOW = 201;
131 
132     /**
133      * Used with {@link #setQuality} to allow high power consumption for location.
134      *
135      * <p>This location request will allow high power location work.
136      */
137     public static final int POWER_HIGH = 203;
138 
139     /**
140      * By default, mFastestInterval = FASTEST_INTERVAL_MULTIPLE * mInterval
141      */
142     private static final double FASTEST_INTERVAL_FACTOR = 6.0;  // 6x
143 
144     private int mQuality = POWER_LOW;
145     private long mInterval = 60 * 60 * 1000;   // 60 minutes
146     private long mFastestInterval = (long) (mInterval / FASTEST_INTERVAL_FACTOR);  // 10 minutes
147     private boolean mExplicitFastestInterval = false;
148     private long mExpireAt = Long.MAX_VALUE;  // no expiry
149     private int mNumUpdates = Integer.MAX_VALUE;  // no expiry
150     private float mSmallestDisplacement = 0.0f;    // meters
151     private WorkSource mWorkSource = null;
152     private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
153 
154     private String mProvider = LocationManager.FUSED_PROVIDER;
155             // for deprecated APIs that explicitly request a provider
156 
157     /** If true, GNSS chipset will make strong tradeoffs to substantially restrict power use */
158     private boolean mLowPowerMode = false;
159 
160     /**
161      * Create a location request with default parameters.
162      *
163      * <p>Default parameters are for a low power, slowly updated location.
164      * It can then be adjusted as required by the applications before passing
165      * to the {@link LocationManager}
166      *
167      * @return a new location request
168      */
create()169     public static LocationRequest create() {
170         LocationRequest request = new LocationRequest();
171         return request;
172     }
173 
174     /** @hide */
175     @SystemApi
createFromDeprecatedProvider(String provider, long minTime, float minDistance, boolean singleShot)176     public static LocationRequest createFromDeprecatedProvider(String provider, long minTime,
177             float minDistance, boolean singleShot) {
178         if (minTime < 0) minTime = 0;
179         if (minDistance < 0) minDistance = 0;
180 
181         int quality;
182         if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
183             quality = POWER_NONE;
184         } else if (LocationManager.GPS_PROVIDER.equals(provider)) {
185             quality = ACCURACY_FINE;
186         } else {
187             quality = POWER_LOW;
188         }
189 
190         LocationRequest request = new LocationRequest()
191                 .setProvider(provider)
192                 .setQuality(quality)
193                 .setInterval(minTime)
194                 .setFastestInterval(minTime)
195                 .setSmallestDisplacement(minDistance);
196         if (singleShot) request.setNumUpdates(1);
197         return request;
198     }
199 
200     /** @hide */
201     @SystemApi
createFromDeprecatedCriteria(Criteria criteria, long minTime, float minDistance, boolean singleShot)202     public static LocationRequest createFromDeprecatedCriteria(Criteria criteria, long minTime,
203             float minDistance, boolean singleShot) {
204         if (minTime < 0) minTime = 0;
205         if (minDistance < 0) minDistance = 0;
206 
207         int quality;
208         switch (criteria.getAccuracy()) {
209             case Criteria.ACCURACY_COARSE:
210                 quality = ACCURACY_BLOCK;
211                 break;
212             case Criteria.ACCURACY_FINE:
213                 quality = ACCURACY_FINE;
214                 break;
215             default: {
216                 switch (criteria.getPowerRequirement()) {
217                     case Criteria.POWER_HIGH:
218                         quality = POWER_HIGH;
219                         break;
220                     default:
221                         quality = POWER_LOW;
222                 }
223             }
224         }
225 
226         LocationRequest request = new LocationRequest()
227                 .setQuality(quality)
228                 .setInterval(minTime)
229                 .setFastestInterval(minTime)
230                 .setSmallestDisplacement(minDistance);
231         if (singleShot) request.setNumUpdates(1);
232         return request;
233     }
234 
235     /** @hide */
LocationRequest()236     public LocationRequest() {
237     }
238 
239     /** @hide */
LocationRequest(LocationRequest src)240     public LocationRequest(LocationRequest src) {
241         mQuality = src.mQuality;
242         mInterval = src.mInterval;
243         mFastestInterval = src.mFastestInterval;
244         mExplicitFastestInterval = src.mExplicitFastestInterval;
245         mExpireAt = src.mExpireAt;
246         mNumUpdates = src.mNumUpdates;
247         mSmallestDisplacement = src.mSmallestDisplacement;
248         mProvider = src.mProvider;
249         mWorkSource = src.mWorkSource;
250         mHideFromAppOps = src.mHideFromAppOps;
251         mLowPowerMode = src.mLowPowerMode;
252     }
253 
254     /**
255      * Set the quality of the request.
256      *
257      * <p>Use with a accuracy constant such as {@link #ACCURACY_FINE}, or a power
258      * constant such as {@link #POWER_LOW}. You cannot request both and accuracy and
259      * power, only one or the other can be specified. The system will then
260      * maximize accuracy or minimize power as appropriate.
261      *
262      * <p>The quality of the request is a strong hint to the system for which
263      * location sources to use. For example, {@link #ACCURACY_FINE} is more likely
264      * to use GPS, and {@link #POWER_LOW} is more likely to use WIFI & Cell tower
265      * positioning, but it also depends on many other factors (such as which sources
266      * are available) and is implementation dependent.
267      *
268      * <p>{@link #setQuality} and {@link #setInterval} are the most important parameters
269      * on a location request.
270      *
271      * @param quality an accuracy or power constant
272      * @return the same object, so that setters can be chained
273      * @throws InvalidArgumentException if the quality constant is not valid
274      */
setQuality(int quality)275     public LocationRequest setQuality(int quality) {
276         checkQuality(quality);
277         mQuality = quality;
278         return this;
279     }
280 
281     /**
282      * Get the quality of the request.
283      *
284      * @return an accuracy or power constant
285      */
getQuality()286     public int getQuality() {
287         return mQuality;
288     }
289 
290     /**
291      * Set the desired interval for active location updates, in milliseconds.
292      *
293      * <p>The location manager will actively try to obtain location updates
294      * for your application at this interval, so it has a
295      * direct influence on the amount of power used by your application.
296      * Choose your interval wisely.
297      *
298      * <p>This interval is inexact. You may not receive updates at all (if
299      * no location sources are available), or you may receive them
300      * slower than requested. You may also receive them faster than
301      * requested (if other applications are requesting location at a
302      * faster interval). The fastest rate that you will receive
303      * updates can be controlled with {@link #setFastestInterval}.
304      *
305      * <p>Applications with only the coarse location permission may have their
306      * interval silently throttled.
307      *
308      * <p>An interval of 0 is allowed, but not recommended, since
309      * location updates may be extremely fast on future implementations.
310      *
311      * <p>{@link #setQuality} and {@link #setInterval} are the most important parameters
312      * on a location request.
313      *
314      * @param millis desired interval in millisecond, inexact
315      * @return the same object, so that setters can be chained
316      * @throws InvalidArgumentException if the interval is less than zero
317      */
setInterval(long millis)318     public LocationRequest setInterval(long millis) {
319         checkInterval(millis);
320         mInterval = millis;
321         if (!mExplicitFastestInterval) {
322             mFastestInterval = (long) (mInterval / FASTEST_INTERVAL_FACTOR);
323         }
324         return this;
325     }
326 
327     /**
328      * Get the desired interval of this request, in milliseconds.
329      *
330      * @return desired interval in milliseconds, inexact
331      */
getInterval()332     public long getInterval() {
333         return mInterval;
334     }
335 
336 
337     /**
338      * Requests the GNSS chipset to run in a low power mode and make strong tradeoffs to
339      * substantially restrict power.
340      *
341      * <p>In this mode, the GNSS chipset will not, on average, run power hungry operations like RF &
342      * signal searches for more than one second per interval (specified by
343      * {@link #setInterval(long)}).
344      *
345      * @param enabled Enable or disable low power mode
346      * @return the same object, so that setters can be chained
347      * @hide
348      */
349     @SystemApi
setLowPowerMode(boolean enabled)350     public LocationRequest setLowPowerMode(boolean enabled) {
351         mLowPowerMode = enabled;
352         return this;
353     }
354 
355     /**
356      * Returns true if low power mode is enabled.
357      *
358      * @hide
359      */
360     @SystemApi
isLowPowerMode()361     public boolean isLowPowerMode() {
362         return mLowPowerMode;
363     }
364 
365     /**
366      * Explicitly set the fastest interval for location updates, in
367      * milliseconds.
368      *
369      * <p>This controls the fastest rate at which your application will
370      * receive location updates, which might be faster than
371      * {@link #setInterval} in some situations (for example, if other
372      * applications are triggering location updates).
373      *
374      * <p>This allows your application to passively acquire locations
375      * at a rate faster than it actively acquires locations, saving power.
376      *
377      * <p>Unlike {@link #setInterval}, this parameter is exact. Your
378      * application will never receive updates faster than this value.
379      *
380      * <p>If you don't call this method, a fastest interval
381      * will be selected for you. It will be a value faster than your
382      * active interval ({@link #setInterval}).
383      *
384      * <p>An interval of 0 is allowed, but not recommended, since
385      * location updates may be extremely fast on future implementations.
386      *
387      * <p>If {@link #setFastestInterval} is set slower than {@link #setInterval},
388      * then your effective fastest interval is {@link #setInterval}.
389      *
390      * @param millis fastest interval for updates in milliseconds, exact
391      * @return the same object, so that setters can be chained
392      * @throws InvalidArgumentException if the interval is less than zero
393      */
setFastestInterval(long millis)394     public LocationRequest setFastestInterval(long millis) {
395         checkInterval(millis);
396         mExplicitFastestInterval = true;
397         mFastestInterval = millis;
398         return this;
399     }
400 
401     /**
402      * Get the fastest interval of this request, in milliseconds.
403      *
404      * <p>The system will never provide location updates faster
405      * than the minimum of {@link #getFastestInterval} and
406      * {@link #getInterval}.
407      *
408      * @return fastest interval in milliseconds, exact
409      */
getFastestInterval()410     public long getFastestInterval() {
411         return mFastestInterval;
412     }
413 
414     /**
415      * Set the duration of this request, in milliseconds.
416      *
417      * <p>The duration begins immediately (and not when the request
418      * is passed to the location manager), so call this method again
419      * if the request is re-used at a later time.
420      *
421      * <p>The location manager will automatically stop updates after
422      * the request expires.
423      *
424      * <p>The duration includes suspend time. Values less than 0
425      * are allowed, but indicate that the request has already expired.
426      *
427      * @param millis duration of request in milliseconds
428      * @return the same object, so that setters can be chained
429      */
setExpireIn(long millis)430     public LocationRequest setExpireIn(long millis) {
431         long elapsedRealtime = SystemClock.elapsedRealtime();
432 
433         // Check for > Long.MAX_VALUE overflow (elapsedRealtime > 0):
434         if (millis > Long.MAX_VALUE - elapsedRealtime) {
435             mExpireAt = Long.MAX_VALUE;
436         } else {
437             mExpireAt = millis + elapsedRealtime;
438         }
439 
440         if (mExpireAt < 0) mExpireAt = 0;
441         return this;
442     }
443 
444     /**
445      * Set the request expiration time, in millisecond since boot.
446      *
447      * <p>This expiration time uses the same time base as {@link SystemClock#elapsedRealtime}.
448      *
449      * <p>The location manager will automatically stop updates after
450      * the request expires.
451      *
452      * <p>The duration includes suspend time. Values before {@link SystemClock#elapsedRealtime}
453      * are allowed,  but indicate that the request has already expired.
454      *
455      * @param millis expiration time of request, in milliseconds since boot including suspend
456      * @return the same object, so that setters can be chained
457      */
setExpireAt(long millis)458     public LocationRequest setExpireAt(long millis) {
459         mExpireAt = millis;
460         if (mExpireAt < 0) mExpireAt = 0;
461         return this;
462     }
463 
464     /**
465      * Get the request expiration time, in milliseconds since boot.
466      *
467      * <p>This value can be compared to {@link SystemClock#elapsedRealtime} to determine
468      * the time until expiration.
469      *
470      * @return expiration time of request, in milliseconds since boot including suspend
471      */
getExpireAt()472     public long getExpireAt() {
473         return mExpireAt;
474     }
475 
476     /**
477      * Set the number of location updates.
478      *
479      * <p>By default locations are continuously updated until the request is explicitly
480      * removed, however you can optionally request a set number of updates.
481      * For example, if your application only needs a single fresh location,
482      * then call this method with a value of 1 before passing the request
483      * to the location manager.
484      *
485      * @param numUpdates the number of location updates requested
486      * @return the same object, so that setters can be chained
487      * @throws InvalidArgumentException if numUpdates is 0 or less
488      */
setNumUpdates(int numUpdates)489     public LocationRequest setNumUpdates(int numUpdates) {
490         if (numUpdates <= 0) {
491             throw new IllegalArgumentException(
492                     "invalid numUpdates: " + numUpdates);
493         }
494         mNumUpdates = numUpdates;
495         return this;
496     }
497 
498     /**
499      * Get the number of updates requested.
500      *
501      * <p>By default this is {@link Integer#MAX_VALUE}, which indicates that
502      * locations are updated until the request is explicitly removed.
503      *
504      * @return number of updates
505      */
getNumUpdates()506     public int getNumUpdates() {
507         return mNumUpdates;
508     }
509 
510     /** @hide */
decrementNumUpdates()511     public void decrementNumUpdates() {
512         if (mNumUpdates != Integer.MAX_VALUE) {
513             mNumUpdates--;
514         }
515         if (mNumUpdates < 0) {
516             mNumUpdates = 0;
517         }
518     }
519 
520 
521     /** @hide */
522     @SystemApi
setProvider(String provider)523     public LocationRequest setProvider(String provider) {
524         checkProvider(provider);
525         mProvider = provider;
526         return this;
527     }
528 
529     /** @hide */
530     @SystemApi
getProvider()531     public String getProvider() {
532         return mProvider;
533     }
534 
535     /** @hide */
536     @SystemApi
setSmallestDisplacement(float meters)537     public LocationRequest setSmallestDisplacement(float meters) {
538         checkDisplacement(meters);
539         mSmallestDisplacement = meters;
540         return this;
541     }
542 
543     /** @hide */
544     @SystemApi
getSmallestDisplacement()545     public float getSmallestDisplacement() {
546         return mSmallestDisplacement;
547     }
548 
549     /**
550      * Sets the WorkSource to use for power blaming of this location request.
551      *
552      * <p>No permissions are required to make this call, however the LocationManager
553      * will throw a SecurityException when requesting location updates if the caller
554      * doesn't have the {@link android.Manifest.permission#UPDATE_DEVICE_STATS} permission.
555      *
556      * @param workSource WorkSource defining power blame for this location request.
557      * @hide
558      */
559     @SystemApi
setWorkSource(WorkSource workSource)560     public void setWorkSource(WorkSource workSource) {
561         mWorkSource = workSource;
562     }
563 
564     /** @hide */
565     @SystemApi
getWorkSource()566     public WorkSource getWorkSource() {
567         return mWorkSource;
568     }
569 
570     /**
571      * Sets whether or not this location request should be hidden from AppOps.
572      *
573      * <p>Hiding a location request from AppOps will remove user visibility in the UI as to this
574      * request's existence.  It does not affect power blaming in the Battery page.
575      *
576      * <p>No permissions are required to make this call, however the LocationManager
577      * will throw a SecurityException when requesting location updates if the caller
578      * doesn't have the {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} permission.
579      *
580      * @param hideFromAppOps If true AppOps won't keep track of this location request.
581      * @hide
582      * @see android.app.AppOpsManager
583      */
584     @SystemApi
setHideFromAppOps(boolean hideFromAppOps)585     public void setHideFromAppOps(boolean hideFromAppOps) {
586         mHideFromAppOps = hideFromAppOps;
587     }
588 
589     /** @hide */
590     @SystemApi
getHideFromAppOps()591     public boolean getHideFromAppOps() {
592         return mHideFromAppOps;
593     }
594 
checkInterval(long millis)595     private static void checkInterval(long millis) {
596         if (millis < 0) {
597             throw new IllegalArgumentException("invalid interval: " + millis);
598         }
599     }
600 
checkQuality(int quality)601     private static void checkQuality(int quality) {
602         switch (quality) {
603             case ACCURACY_FINE:
604             case ACCURACY_BLOCK:
605             case ACCURACY_CITY:
606             case POWER_NONE:
607             case POWER_LOW:
608             case POWER_HIGH:
609                 break;
610             default:
611                 throw new IllegalArgumentException("invalid quality: " + quality);
612         }
613     }
614 
checkDisplacement(float meters)615     private static void checkDisplacement(float meters) {
616         if (meters < 0.0f) {
617             throw new IllegalArgumentException("invalid displacement: " + meters);
618         }
619     }
620 
checkProvider(String name)621     private static void checkProvider(String name) {
622         if (name == null) {
623             throw new IllegalArgumentException("invalid provider: " + name);
624         }
625     }
626 
627     public static final Parcelable.Creator<LocationRequest> CREATOR =
628             new Parcelable.Creator<LocationRequest>() {
629                 @Override
630                 public LocationRequest createFromParcel(Parcel in) {
631                     LocationRequest request = new LocationRequest();
632                     request.setQuality(in.readInt());
633                     request.setFastestInterval(in.readLong());
634                     request.setInterval(in.readLong());
635                     request.setExpireAt(in.readLong());
636                     request.setNumUpdates(in.readInt());
637                     request.setSmallestDisplacement(in.readFloat());
638                     request.setHideFromAppOps(in.readInt() != 0);
639                     request.setLowPowerMode(in.readInt() != 0);
640                     String provider = in.readString();
641                     if (provider != null) request.setProvider(provider);
642                     WorkSource workSource = in.readParcelable(null);
643                     if (workSource != null) request.setWorkSource(workSource);
644                     return request;
645                 }
646 
647                 @Override
648                 public LocationRequest[] newArray(int size) {
649                     return new LocationRequest[size];
650                 }
651             };
652 
653     @Override
describeContents()654     public int describeContents() {
655         return 0;
656     }
657 
658     @Override
writeToParcel(Parcel parcel, int flags)659     public void writeToParcel(Parcel parcel, int flags) {
660         parcel.writeInt(mQuality);
661         parcel.writeLong(mFastestInterval);
662         parcel.writeLong(mInterval);
663         parcel.writeLong(mExpireAt);
664         parcel.writeInt(mNumUpdates);
665         parcel.writeFloat(mSmallestDisplacement);
666         parcel.writeInt(mHideFromAppOps ? 1 : 0);
667         parcel.writeInt(mLowPowerMode ? 1 : 0);
668         parcel.writeString(mProvider);
669         parcel.writeParcelable(mWorkSource, 0);
670     }
671 
672     /** @hide */
qualityToString(int quality)673     public static String qualityToString(int quality) {
674         switch (quality) {
675             case ACCURACY_FINE:
676                 return "ACCURACY_FINE";
677             case ACCURACY_BLOCK:
678                 return "ACCURACY_BLOCK";
679             case ACCURACY_CITY:
680                 return "ACCURACY_CITY";
681             case POWER_NONE:
682                 return "POWER_NONE";
683             case POWER_LOW:
684                 return "POWER_LOW";
685             case POWER_HIGH:
686                 return "POWER_HIGH";
687             default:
688                 return "???";
689         }
690     }
691 
692     @Override
toString()693     public String toString() {
694         StringBuilder s = new StringBuilder();
695         s.append("Request[").append(qualityToString(mQuality));
696         if (mProvider != null) s.append(' ').append(mProvider);
697         if (mQuality != POWER_NONE) {
698             s.append(" requested=");
699             TimeUtils.formatDuration(mInterval, s);
700         }
701         s.append(" fastest=");
702         TimeUtils.formatDuration(mFastestInterval, s);
703         if (mExpireAt != Long.MAX_VALUE) {
704             long expireIn = mExpireAt - SystemClock.elapsedRealtime();
705             s.append(" expireIn=");
706             TimeUtils.formatDuration(expireIn, s);
707         }
708         if (mNumUpdates != Integer.MAX_VALUE) {
709             s.append(" num=").append(mNumUpdates);
710         }
711         s.append(" lowPowerMode=").append(mLowPowerMode);
712         s.append(']');
713         return s.toString();
714     }
715 }
716