• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.location.fused;
18 
19 import static android.content.Intent.ACTION_USER_SWITCHED;
20 import static android.location.LocationManager.GPS_PROVIDER;
21 import static android.location.LocationManager.NETWORK_PROVIDER;
22 import static android.location.LocationRequest.QUALITY_LOW_POWER;
23 import static android.location.provider.ProviderProperties.ACCURACY_FINE;
24 import static android.location.provider.ProviderProperties.POWER_USAGE_LOW;
25 
26 import static com.android.location.provider.ProviderRequestUnbundled.INTERVAL_DISABLED;
27 
28 import android.annotation.Nullable;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.location.Location;
34 import android.location.LocationListener;
35 import android.location.LocationManager;
36 import android.location.LocationRequest;
37 import android.location.provider.LocationProviderBase;
38 import android.location.provider.ProviderProperties;
39 import android.location.provider.ProviderRequest;
40 import android.os.Bundle;
41 import android.util.SparseArray;
42 
43 import com.android.internal.annotations.GuardedBy;
44 
45 import java.io.PrintWriter;
46 import java.util.Objects;
47 import java.util.concurrent.atomic.AtomicInteger;
48 
49 /** Basic fused location provider implementation. */
50 public class FusedLocationProvider extends LocationProviderBase {
51 
52     private static final String TAG = "FusedLocationProvider";
53 
54     private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder()
55                 .setHasAltitudeSupport(true)
56                 .setHasSpeedSupport(true)
57                 .setHasBearingSupport(true)
58                 .setPowerUsage(POWER_USAGE_LOW)
59                 .setAccuracy(ACCURACY_FINE)
60                 .build();
61 
62     private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds
63 
64     private final Object mLock = new Object();
65 
66     private final Context mContext;
67     private final LocationManager mLocationManager;
68     private final ChildLocationListener mGpsListener;
69     private final ChildLocationListener mNetworkListener;
70     private final BroadcastReceiver mUserChangeReceiver;
71 
72     @GuardedBy("mLock")
73     boolean mGpsPresent;
74 
75     @GuardedBy("mLock")
76     boolean mNlpPresent;
77 
78     @GuardedBy("mLock")
79     private ProviderRequest mRequest;
80 
81     @GuardedBy("mLock")
82     private @Nullable Location mFusedLocation;
83 
FusedLocationProvider(Context context)84     public FusedLocationProvider(Context context) {
85         super(context, TAG, PROPERTIES);
86         mContext = context;
87         mLocationManager = Objects.requireNonNull(context.getSystemService(LocationManager.class));
88 
89         mGpsListener = new ChildLocationListener(GPS_PROVIDER);
90         mNetworkListener = new ChildLocationListener(NETWORK_PROVIDER);
91 
92         mUserChangeReceiver = new BroadcastReceiver() {
93             @Override
94             public void onReceive(Context context, Intent intent) {
95                 if (!ACTION_USER_SWITCHED.equals(intent.getAction())) {
96                     return;
97                 }
98 
99                 onUserChanged();
100             }
101         };
102 
103         mRequest = ProviderRequest.EMPTY_REQUEST;
104     }
105 
start()106     void start() {
107         mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED));
108     }
109 
stop()110     void stop() {
111         mContext.unregisterReceiver(mUserChangeReceiver);
112 
113         synchronized (mLock) {
114             mRequest = ProviderRequest.EMPTY_REQUEST;
115             updateRequirementsLocked();
116         }
117     }
118 
119     @Override
onSetRequest(ProviderRequest request)120     public void onSetRequest(ProviderRequest request) {
121         synchronized (mLock) {
122             mRequest = request;
123             updateRequirementsLocked();
124         }
125     }
126 
127     @Override
onFlush(OnFlushCompleteCallback callback)128     public void onFlush(OnFlushCompleteCallback callback) {
129         synchronized (mLock) {
130             AtomicInteger flushCount = new AtomicInteger(0);
131             if (mGpsPresent) {
132                 flushCount.incrementAndGet();
133             }
134             if (mNlpPresent) {
135                 flushCount.incrementAndGet();
136             }
137 
138             OnFlushCompleteCallback wrapper = () -> {
139                 if (flushCount.decrementAndGet() == 0) {
140                     callback.onFlushComplete();
141                 }
142             };
143 
144             if (mGpsPresent) {
145                 mGpsListener.flush(wrapper);
146             }
147             if (mNlpPresent) {
148                 mNetworkListener.flush(wrapper);
149             }
150         }
151     }
152 
153     @Override
onSendExtraCommand(String command, @Nullable Bundle extras)154     public void onSendExtraCommand(String command, @Nullable Bundle extras) {}
155 
156     @GuardedBy("mLock")
updateRequirementsLocked()157     private void updateRequirementsLocked() {
158         // it's possible there might be race conditions on device start where a provider doesn't
159         // appear to be present yet, but once a provider is present it shouldn't go away.
160         if (!mGpsPresent) {
161             mGpsPresent = mLocationManager.hasProvider(GPS_PROVIDER);
162         }
163         if (!mNlpPresent) {
164             mNlpPresent = mLocationManager.hasProvider(NETWORK_PROVIDER);
165         }
166 
167         long gpsInterval =
168                 mGpsPresent && (!mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER)
169                         ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
170         long networkInterval = mNlpPresent ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
171 
172         mGpsListener.resetProviderRequest(gpsInterval);
173         mNetworkListener.resetProviderRequest(networkInterval);
174     }
175 
176     @GuardedBy("mLock")
reportBestLocationLocked()177     void reportBestLocationLocked() {
178         Location bestLocation = chooseBestLocation(mGpsListener.getLocation(),
179                 mNetworkListener.getLocation());
180         if (bestLocation == mFusedLocation) {
181             return;
182         }
183 
184         mFusedLocation = bestLocation;
185         if (mFusedLocation == null) {
186             return;
187         }
188 
189         reportLocation(mFusedLocation);
190     }
191 
onUserChanged()192     void onUserChanged() {
193         // clear cached locations when the user changes to prevent leaking user information
194         synchronized (mLock) {
195             mFusedLocation = null;
196             mGpsListener.clearLocation();
197             mNetworkListener.clearLocation();
198         }
199     }
200 
dump(PrintWriter writer)201     void dump(PrintWriter writer) {
202         synchronized (mLock) {
203             writer.println("request: " + mRequest);
204             if (mGpsListener.getInterval() != INTERVAL_DISABLED) {
205                 writer.println("  gps interval: " + mGpsListener.getInterval());
206             }
207             if (mNetworkListener.getInterval() != INTERVAL_DISABLED) {
208                 writer.println("  network interval: " + mNetworkListener.getInterval());
209             }
210             if (mGpsListener.getLocation() != null) {
211                 writer.println("  last gps location: " + mGpsListener.getLocation());
212             }
213             if (mNetworkListener.getLocation() != null) {
214                 writer.println("  last network location: " + mNetworkListener.getLocation());
215             }
216         }
217     }
218 
219     @Nullable
chooseBestLocation( @ullable Location locationA, @Nullable Location locationB)220     private static Location chooseBestLocation(
221             @Nullable Location locationA,
222             @Nullable Location locationB) {
223         if (locationA == null) {
224             return locationB;
225         }
226         if (locationB == null) {
227             return locationA;
228         }
229 
230         if (locationA.getElapsedRealtimeNanos()
231                 > locationB.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
232             return locationA;
233         }
234         if (locationB.getElapsedRealtimeNanos()
235                 > locationA.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
236             return locationB;
237         }
238 
239         if (!locationA.hasAccuracy()) {
240             return locationB;
241         }
242         if (!locationB.hasAccuracy()) {
243             return locationA;
244         }
245         return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB;
246     }
247 
248     private class ChildLocationListener implements LocationListener {
249 
250         private final String mProvider;
251         private final SparseArray<OnFlushCompleteCallback> mPendingFlushes;
252 
253         @GuardedBy("mLock")
254         private int mNextFlushCode = 0;
255         @GuardedBy("mLock")
256         private @Nullable Location mLocation = null;
257         @GuardedBy("mLock")
258         private long mInterval = INTERVAL_DISABLED;
259 
ChildLocationListener(String provider)260         ChildLocationListener(String provider) {
261             mProvider = provider;
262             mPendingFlushes = new SparseArray<>();
263         }
264 
getLocation()265         @Nullable Location getLocation() {
266             synchronized (mLock) {
267                 return mLocation;
268             }
269         }
270 
getInterval()271         long getInterval() {
272             synchronized (mLock) {
273                 return mInterval;
274             }
275         }
276 
clearLocation()277         void clearLocation() {
278             synchronized (mLock) {
279                 mLocation = null;
280             }
281         }
282 
resetProviderRequest(long newInterval)283         private void resetProviderRequest(long newInterval) {
284             synchronized (mLock) {
285                 if (newInterval == mInterval) {
286                     return;
287                 }
288 
289                 if (mInterval != INTERVAL_DISABLED && newInterval == INTERVAL_DISABLED) {
290                     mLocationManager.removeUpdates(this);
291                 }
292 
293                 mInterval = newInterval;
294 
295                 if (mInterval != INTERVAL_DISABLED) {
296                     LocationRequest request = new LocationRequest.Builder(mInterval)
297                             .setMaxUpdateDelayMillis(mRequest.getMaxUpdateDelayMillis())
298                             .setQuality(mRequest.getQuality())
299                             .setLowPower(mRequest.isLowPower())
300                             .setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored())
301                             .setWorkSource(mRequest.getWorkSource())
302                             .setHiddenFromAppOps(true)
303                             .build();
304                     mLocationManager.requestLocationUpdates(mProvider, request,
305                             mContext.getMainExecutor(), this);
306                 }
307             }
308         }
309 
flush(OnFlushCompleteCallback callback)310         void flush(OnFlushCompleteCallback callback) {
311             synchronized (mLock) {
312                 int requestCode = mNextFlushCode++;
313                 mPendingFlushes.put(requestCode, callback);
314                 mLocationManager.requestFlush(mProvider, this, requestCode);
315             }
316         }
317 
318         @Override
onLocationChanged(Location location)319         public void onLocationChanged(Location location) {
320             synchronized (mLock) {
321                 mLocation = location;
322                 reportBestLocationLocked();
323             }
324         }
325 
326         @Override
onProviderDisabled(String provider)327         public void onProviderDisabled(String provider) {
328             synchronized (mLock) {
329                 // if satisfying a bypass request, don't clear anything
330                 if (mRequest.isActive() && mRequest.isLocationSettingsIgnored()) {
331                     return;
332                 }
333 
334                 mLocation = null;
335             }
336         }
337 
338         @Override
onFlushComplete(int requestCode)339         public void onFlushComplete(int requestCode) {
340             synchronized (mLock) {
341                 OnFlushCompleteCallback callback = mPendingFlushes.removeReturnOld(requestCode);
342                 if (callback != null) {
343                     callback.onFlushComplete();
344                 }
345             }
346         }
347     }
348 }
349