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 
23 import android.annotation.Nullable;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.location.Criteria;
29 import android.location.Location;
30 import android.location.LocationListener;
31 import android.location.LocationManager;
32 import android.location.LocationRequest;
33 import android.os.Bundle;
34 import android.os.Looper;
35 import android.os.Parcelable;
36 import android.os.WorkSource;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.location.ProviderRequest;
40 import com.android.location.provider.LocationProviderBase;
41 import com.android.location.provider.LocationRequestUnbundled;
42 import com.android.location.provider.ProviderPropertiesUnbundled;
43 import com.android.location.provider.ProviderRequestUnbundled;
44 
45 import java.io.PrintWriter;
46 
47 /** Basic fused location provider implementation. */
48 public class FusedLocationProvider extends LocationProviderBase {
49 
50     private static final String TAG = "FusedLocationProvider";
51 
52     private static final ProviderPropertiesUnbundled PROPERTIES =
53             ProviderPropertiesUnbundled.create(
54                     /* requiresNetwork = */ false,
55                     /* requiresSatellite = */ false,
56                     /* requiresCell = */ false,
57                     /* hasMonetaryCost = */ false,
58                     /* supportsAltitude = */ true,
59                     /* supportsSpeed = */ true,
60                     /* supportsBearing = */ true,
61                     Criteria.POWER_LOW,
62                     Criteria.ACCURACY_FINE
63             );
64 
65     private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds
66 
67     private final Object mLock = new Object();
68 
69     private final Context mContext;
70     private final LocationManager mLocationManager;
71     private final LocationListener mGpsListener;
72     private final LocationListener mNetworkListener;
73     private final BroadcastReceiver mUserChangeReceiver;
74 
75     @GuardedBy("mLock")
76     private ProviderRequestUnbundled mRequest;
77     @GuardedBy("mLock")
78     private WorkSource mWorkSource;
79     @GuardedBy("mLock")
80     private long mGpsInterval;
81     @GuardedBy("mLock")
82     private long mNetworkInterval;
83 
84     @GuardedBy("mLock")
85     @Nullable private Location mFusedLocation;
86     @GuardedBy("mLock")
87     @Nullable private Location mGpsLocation;
88     @GuardedBy("mLock")
89     @Nullable private Location mNetworkLocation;
90 
FusedLocationProvider(Context context)91     public FusedLocationProvider(Context context) {
92         super(TAG, PROPERTIES);
93         mContext = context;
94         mLocationManager = context.getSystemService(LocationManager.class);
95 
96         mGpsListener = new LocationListener() {
97             @Override
98             public void onLocationChanged(Location location) {
99                 synchronized (mLock) {
100                     mGpsLocation = location;
101                     reportBestLocationLocked();
102                 }
103             }
104 
105             @Override
106             public void onProviderDisabled(String provider) {
107                 synchronized (mLock) {
108                     // if satisfying a bypass request, don't clear anything
109                     if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
110                         return;
111                     }
112 
113                     mGpsLocation = null;
114                 }
115             }
116         };
117 
118         mNetworkListener = new LocationListener() {
119             @Override
120             public void onLocationChanged(Location location) {
121                 synchronized (mLock) {
122                     mNetworkLocation = location;
123                     reportBestLocationLocked();
124                 }
125             }
126 
127             @Override
128             public void onProviderDisabled(String provider) {
129                 synchronized (mLock) {
130                     // if satisfying a bypass request, don't clear anything
131                     if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
132                         return;
133                     }
134 
135                     mNetworkLocation = null;
136                 }
137             }
138         };
139 
140         mUserChangeReceiver = new BroadcastReceiver() {
141             @Override
142             public void onReceive(Context context, Intent intent) {
143                 if (!ACTION_USER_SWITCHED.equals(intent.getAction())) {
144                     return;
145                 }
146 
147                 onUserChanged();
148             }
149         };
150 
151         mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
152         mWorkSource = new WorkSource();
153         mGpsInterval = Long.MAX_VALUE;
154         mNetworkInterval = Long.MAX_VALUE;
155     }
156 
start()157     void start() {
158         mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED));
159     }
160 
stop()161     void stop() {
162         mContext.unregisterReceiver(mUserChangeReceiver);
163 
164         synchronized (mLock) {
165             mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
166             updateRequirementsLocked();
167         }
168     }
169 
170     @Override
onSetRequest(ProviderRequestUnbundled request, WorkSource workSource)171     public void onSetRequest(ProviderRequestUnbundled request, WorkSource workSource) {
172         synchronized (mLock) {
173             mRequest = request;
174             mWorkSource = workSource;
175             updateRequirementsLocked();
176         }
177     }
178 
179     @GuardedBy("mLock")
updateRequirementsLocked()180     private void updateRequirementsLocked() {
181         long gpsInterval = Long.MAX_VALUE;
182         long networkInterval = Long.MAX_VALUE;
183         if (mRequest.getReportLocation()) {
184             for (LocationRequestUnbundled request : mRequest.getLocationRequests()) {
185                 switch (request.getQuality()) {
186                     case LocationRequestUnbundled.ACCURACY_FINE:
187                     case LocationRequestUnbundled.POWER_HIGH:
188                         if (request.getInterval() < gpsInterval) {
189                             gpsInterval = request.getInterval();
190                         }
191                         if (request.getInterval() < networkInterval) {
192                             networkInterval = request.getInterval();
193                         }
194                         break;
195                     case LocationRequestUnbundled.ACCURACY_BLOCK:
196                     case LocationRequestUnbundled.ACCURACY_CITY:
197                     case LocationRequestUnbundled.POWER_LOW:
198                         if (request.getInterval() < networkInterval) {
199                             networkInterval = request.getInterval();
200                         }
201                         break;
202                 }
203             }
204         }
205 
206         if (gpsInterval != mGpsInterval) {
207             resetProviderRequestLocked(GPS_PROVIDER, mGpsInterval, gpsInterval, mGpsListener);
208             mGpsInterval = gpsInterval;
209         }
210         if (networkInterval != mNetworkInterval) {
211             resetProviderRequestLocked(NETWORK_PROVIDER, mNetworkInterval, networkInterval,
212                     mNetworkListener);
213             mNetworkInterval = networkInterval;
214         }
215     }
216 
217     @GuardedBy("mLock")
resetProviderRequestLocked(String provider, long oldInterval, long newInterval, LocationListener listener)218     private void resetProviderRequestLocked(String provider, long oldInterval, long newInterval,
219             LocationListener listener) {
220         if (oldInterval != Long.MAX_VALUE) {
221             mLocationManager.removeUpdates(listener);
222         }
223         if (newInterval != Long.MAX_VALUE) {
224             LocationRequest request = LocationRequest.createFromDeprecatedProvider(
225                     provider, newInterval, 0, false);
226             if (mRequest.isLocationSettingsIgnored()) {
227                 request.setLocationSettingsIgnored(true);
228             }
229             request.setWorkSource(mWorkSource);
230             mLocationManager.requestLocationUpdates(request, listener, Looper.getMainLooper());
231         }
232     }
233 
234     @GuardedBy("mLock")
reportBestLocationLocked()235     private void reportBestLocationLocked() {
236         Location bestLocation = chooseBestLocation(mGpsLocation, mNetworkLocation);
237         if (bestLocation == mFusedLocation) {
238             return;
239         }
240 
241         mFusedLocation = bestLocation;
242         if (mFusedLocation == null) {
243             return;
244         }
245 
246         // copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation
247         if (mNetworkLocation != null) {
248             Bundle srcExtras = mNetworkLocation.getExtras();
249             if (srcExtras != null) {
250                 Parcelable srcParcelable =
251                         srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION);
252                 if (srcParcelable instanceof Location) {
253                     Bundle dstExtras = mFusedLocation.getExtras();
254                     if (dstExtras == null) {
255                         dstExtras = new Bundle();
256                         mFusedLocation.setExtras(dstExtras);
257                     }
258                     dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION,
259                             srcParcelable);
260                 }
261             }
262         }
263 
264         reportLocation(mFusedLocation);
265     }
266 
onUserChanged()267     private void onUserChanged() {
268         // clear cached locations when the user changes to prevent leaking user information
269         synchronized (mLock) {
270             mFusedLocation = null;
271             mGpsLocation = null;
272             mNetworkLocation = null;
273         }
274     }
275 
dump(PrintWriter writer)276     void dump(PrintWriter writer) {
277         synchronized (mLock) {
278             writer.println("request: " + mRequest);
279             if (mGpsInterval != Long.MAX_VALUE) {
280                 writer.println("  gps interval: " + mGpsInterval);
281             }
282             if (mNetworkInterval != Long.MAX_VALUE) {
283                 writer.println("  network interval: " + mNetworkInterval);
284             }
285             if (mGpsLocation != null) {
286                 writer.println("  last gps location: " + mGpsLocation);
287             }
288             if (mNetworkLocation != null) {
289                 writer.println("  last network location: " + mNetworkLocation);
290             }
291         }
292     }
293 
294     @Nullable
chooseBestLocation( @ullable Location locationA, @Nullable Location locationB)295     private static Location chooseBestLocation(
296             @Nullable Location locationA,
297             @Nullable Location locationB) {
298         if (locationA == null) {
299             return locationB;
300         }
301         if (locationB == null) {
302             return locationA;
303         }
304 
305         if (locationA.getElapsedRealtimeNanos()
306                 > locationB.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
307             return locationA;
308         }
309         if (locationB.getElapsedRealtimeNanos()
310                 > locationA.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
311             return locationB;
312         }
313 
314         if (!locationA.hasAccuracy()) {
315             return locationB;
316         }
317         if (!locationB.hasAccuracy()) {
318             return locationA;
319         }
320         return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB;
321     }
322 }
323