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