• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.car;
18 
19 import android.app.ActivityManager;
20 import android.car.ILocationManagerProxy;
21 import android.car.IPerUserCarService;
22 import android.car.drivingstate.CarDrivingStateEvent;
23 import android.car.drivingstate.ICarDrivingStateChangeListener;
24 import android.car.hardware.power.CarPowerManager;
25 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
26 import android.car.hardware.power.CarPowerManager.CarPowerStateListenerWithCompletion;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.location.Location;
32 import android.location.LocationManager;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.RemoteException;
36 import android.os.SystemClock;
37 import android.os.UserHandle;
38 import android.os.UserManager;
39 import android.util.AtomicFile;
40 import android.util.JsonReader;
41 import android.util.JsonWriter;
42 import android.util.Log;
43 
44 import com.android.car.systeminterface.SystemInterface;
45 import com.android.internal.annotations.VisibleForTesting;
46 
47 import java.io.File;
48 import java.io.FileInputStream;
49 import java.io.FileNotFoundException;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
52 import java.io.InputStreamReader;
53 import java.io.OutputStreamWriter;
54 import java.io.PrintWriter;
55 import java.util.concurrent.CompletableFuture;
56 
57 /**
58  * This service stores the last known location from {@link LocationManager} when a car is parked
59  * and restores the location when the car is powered on.
60  */
61 public class CarLocationService extends BroadcastReceiver implements CarServiceBase,
62         CarPowerStateListenerWithCompletion {
63     private static final String TAG = "CarLocationService";
64     private static final String FILENAME = "location_cache.json";
65     // The accuracy for the stored timestamp
66     private static final long GRANULARITY_ONE_DAY_MS = 24 * 60 * 60 * 1000L;
67     // The time-to-live for the cached location
68     private static final long TTL_THIRTY_DAYS_MS = 30 * GRANULARITY_ONE_DAY_MS;
69     // The maximum number of times to try injecting a location
70     private static final int MAX_LOCATION_INJECTION_ATTEMPTS = 10;
71 
72     // Constants for location serialization.
73     private static final String PROVIDER = "provider";
74     private static final String LATITUDE = "latitude";
75     private static final String LONGITUDE = "longitude";
76     private static final String ALTITUDE = "altitude";
77     private static final String SPEED = "speed";
78     private static final String BEARING = "bearing";
79     private static final String ACCURACY = "accuracy";
80     private static final String VERTICAL_ACCURACY = "verticalAccuracy";
81     private static final String SPEED_ACCURACY = "speedAccuracy";
82     private static final String BEARING_ACCURACY = "bearingAccuracy";
83     private static final String IS_FROM_MOCK_PROVIDER = "isFromMockProvider";
84     private static final String CAPTURE_TIME = "captureTime";
85 
86     // Used internally for mHandlerThread synchronization
87     private final Object mLock = new Object();
88 
89     // Used internally for mILocationManagerProxy synchronization
90     private final Object mLocationManagerProxyLock = new Object();
91 
92     private final Context mContext;
93     private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
94             getClass().getSimpleName());
95     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
96 
97     private CarPowerManager mCarPowerManager;
98     private CarDrivingStateService mCarDrivingStateService;
99     private PerUserCarServiceHelper mPerUserCarServiceHelper;
100 
101     // Allows us to interact with the {@link LocationManager} as the foreground user.
102     private ILocationManagerProxy mILocationManagerProxy;
103 
104     // Maintains mILocationManagerProxy for the current foreground user.
105     private final PerUserCarServiceHelper.ServiceCallback mUserServiceCallback =
106             new PerUserCarServiceHelper.ServiceCallback() {
107                 @Override
108                 public void onServiceConnected(IPerUserCarService perUserCarService) {
109                     logd("Connected to PerUserCarService");
110                     if (perUserCarService == null) {
111                         logd("IPerUserCarService is null. Cannot get location manager proxy");
112                         return;
113                     }
114                     synchronized (mLocationManagerProxyLock) {
115                         try {
116                             mILocationManagerProxy = perUserCarService.getLocationManagerProxy();
117                         } catch (RemoteException e) {
118                             Log.e(TAG, "RemoteException from IPerUserCarService", e);
119                             return;
120                         }
121                     }
122                     int currentUser = ActivityManager.getCurrentUser();
123                     logd("Current user: %s", currentUser);
124                     if (UserManager.isHeadlessSystemUserMode()
125                             && currentUser > UserHandle.USER_SYSTEM) {
126                         asyncOperation(() -> loadLocation());
127                     }
128                 }
129 
130                 @Override
131                 public void onPreUnbind() {
132                     logd("Before Unbinding from PerUserCarService");
133                     synchronized (mLocationManagerProxyLock) {
134                         mILocationManagerProxy = null;
135                     }
136                 }
137 
138                 @Override
139                 public void onServiceDisconnected() {
140                     logd("Disconnected from PerUserCarService");
141                     synchronized (mLocationManagerProxyLock) {
142                         mILocationManagerProxy = null;
143                     }
144                 }
145             };
146 
147     private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
148             new ICarDrivingStateChangeListener.Stub() {
149                 @Override
150                 public void onDrivingStateChanged(CarDrivingStateEvent event) {
151                     logd("onDrivingStateChanged: %s", event);
152                     if (event != null
153                             && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
154                         deleteCacheFile();
155                         if (mCarDrivingStateService != null) {
156                             mCarDrivingStateService.unregisterDrivingStateChangeListener(
157                                     mICarDrivingStateChangeEventListener);
158                         }
159                     }
160                 }
161             };
162 
CarLocationService(Context context)163     public CarLocationService(Context context) {
164         logd("constructed");
165         mContext = context;
166     }
167 
168     @Override
init()169     public void init() {
170         logd("init");
171         IntentFilter filter = new IntentFilter();
172         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
173         mContext.registerReceiver(this, filter);
174         mCarDrivingStateService = CarLocalServices.getService(CarDrivingStateService.class);
175         if (mCarDrivingStateService != null) {
176             CarDrivingStateEvent event = mCarDrivingStateService.getCurrentDrivingState();
177             if (event != null && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
178                 deleteCacheFile();
179             } else {
180                 mCarDrivingStateService.registerDrivingStateChangeListener(
181                         mICarDrivingStateChangeEventListener);
182             }
183         }
184         mCarPowerManager = CarLocalServices.createCarPowerManager(mContext);
185         if (mCarPowerManager != null) { // null case happens for testing.
186             mCarPowerManager.setListenerWithCompletion(CarLocationService.this);
187         }
188         mPerUserCarServiceHelper = CarLocalServices.getService(PerUserCarServiceHelper.class);
189         if (mPerUserCarServiceHelper != null) {
190             mPerUserCarServiceHelper.registerServiceCallback(mUserServiceCallback);
191         }
192     }
193 
194     @Override
release()195     public void release() {
196         logd("release");
197         if (mCarPowerManager != null) {
198             mCarPowerManager.clearListener();
199         }
200         if (mCarDrivingStateService != null) {
201             mCarDrivingStateService.unregisterDrivingStateChangeListener(
202                     mICarDrivingStateChangeEventListener);
203         }
204         if (mPerUserCarServiceHelper != null) {
205             mPerUserCarServiceHelper.unregisterServiceCallback(mUserServiceCallback);
206         }
207         mContext.unregisterReceiver(this);
208     }
209 
210     @Override
dump(PrintWriter writer)211     public void dump(PrintWriter writer) {
212         writer.println(TAG);
213         writer.println("Context: " + mContext);
214         writer.println("MAX_LOCATION_INJECTION_ATTEMPTS: " + MAX_LOCATION_INJECTION_ATTEMPTS);
215     }
216 
217     @Override
onStateChanged(int state, CompletableFuture<Void> future)218     public void onStateChanged(int state, CompletableFuture<Void> future) {
219         logd("onStateChanged: %s", state);
220         switch (state) {
221             case CarPowerStateListener.SHUTDOWN_PREPARE:
222                 asyncOperation(() -> {
223                     storeLocation();
224                     // Notify the CarPowerManager that it may proceed to shutdown or suspend.
225                     if (future != null) {
226                         future.complete(null);
227                     }
228                 });
229                 break;
230             case CarPowerStateListener.SUSPEND_EXIT:
231                 if (mCarDrivingStateService != null) {
232                     CarDrivingStateEvent event = mCarDrivingStateService.getCurrentDrivingState();
233                     if (event != null
234                             && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
235                         deleteCacheFile();
236                     } else {
237                         logd("Registering to receive driving state.");
238                         mCarDrivingStateService.registerDrivingStateChangeListener(
239                                 mICarDrivingStateChangeEventListener);
240                     }
241                 }
242                 if (future != null) {
243                     future.complete(null);
244                 }
245             default:
246                 // This service does not need to do any work for these events but should still
247                 // notify the CarPowerManager that it may proceed.
248                 if (future != null) {
249                     future.complete(null);
250                 }
251                 break;
252         }
253     }
254 
255     @Override
onReceive(Context context, Intent intent)256     public void onReceive(Context context, Intent intent) {
257         logd("onReceive %s", intent);
258         // If the system user is headless but the current user is still the system user, then we
259         // should not delete the location cache file due to missing location permissions.
260         if (isCurrentUserHeadlessSystemUser()) {
261             logd("Current user is headless system user.");
262             return;
263         }
264         synchronized (mLocationManagerProxyLock) {
265             if (mILocationManagerProxy == null) {
266                 logd("Null location manager.");
267                 return;
268             }
269             String action = intent.getAction();
270             try {
271                 if (action == LocationManager.MODE_CHANGED_ACTION) {
272                     boolean locationEnabled = mILocationManagerProxy.isLocationEnabled();
273                     logd("isLocationEnabled(): %s", locationEnabled);
274                     if (!locationEnabled) {
275                         deleteCacheFile();
276                     }
277                 } else {
278                     logd("Unexpected intent.");
279                 }
280             } catch (RemoteException e) {
281                 Log.e(TAG, "RemoteException from ILocationManagerProxy", e);
282             }
283         }
284     }
285 
286     /** Tells whether the current foreground user is the headless system user. */
isCurrentUserHeadlessSystemUser()287     private boolean isCurrentUserHeadlessSystemUser() {
288         int currentUserId = ActivityManager.getCurrentUser();
289         return UserManager.isHeadlessSystemUserMode()
290                 && currentUserId == UserHandle.USER_SYSTEM;
291     }
292 
293     /**
294      * Gets the last known location from the location manager proxy and store it in a file.
295      */
storeLocation()296     private void storeLocation() {
297         Location location = null;
298         synchronized (mLocationManagerProxyLock) {
299             if (mILocationManagerProxy == null) {
300                 logd("Null location manager proxy.");
301                 return;
302             }
303             try {
304                 location = mILocationManagerProxy.getLastKnownLocation(
305                         LocationManager.GPS_PROVIDER);
306             } catch (RemoteException e) {
307                 Log.e(TAG, "RemoteException from ILocationManagerProxy", e);
308             }
309         }
310         if (location == null) {
311             logd("Not storing null location");
312         } else {
313             logd("Storing location");
314             AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
315             FileOutputStream fos = null;
316             try {
317                 fos = atomicFile.startWrite();
318                 try (JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(fos, "UTF-8"))) {
319                     jsonWriter.beginObject();
320                     jsonWriter.name(PROVIDER).value(location.getProvider());
321                     jsonWriter.name(LATITUDE).value(location.getLatitude());
322                     jsonWriter.name(LONGITUDE).value(location.getLongitude());
323                     if (location.hasAltitude()) {
324                         jsonWriter.name(ALTITUDE).value(location.getAltitude());
325                     }
326                     if (location.hasSpeed()) {
327                         jsonWriter.name(SPEED).value(location.getSpeed());
328                     }
329                     if (location.hasBearing()) {
330                         jsonWriter.name(BEARING).value(location.getBearing());
331                     }
332                     if (location.hasAccuracy()) {
333                         jsonWriter.name(ACCURACY).value(location.getAccuracy());
334                     }
335                     if (location.hasVerticalAccuracy()) {
336                         jsonWriter.name(VERTICAL_ACCURACY).value(
337                                 location.getVerticalAccuracyMeters());
338                     }
339                     if (location.hasSpeedAccuracy()) {
340                         jsonWriter.name(SPEED_ACCURACY).value(
341                                 location.getSpeedAccuracyMetersPerSecond());
342                     }
343                     if (location.hasBearingAccuracy()) {
344                         jsonWriter.name(BEARING_ACCURACY).value(
345                                 location.getBearingAccuracyDegrees());
346                     }
347                     if (location.isFromMockProvider()) {
348                         jsonWriter.name(IS_FROM_MOCK_PROVIDER).value(true);
349                     }
350                     long currentTime = location.getTime();
351                     // Round the time down to only be accurate within one day.
352                     jsonWriter.name(CAPTURE_TIME).value(
353                             currentTime - currentTime % GRANULARITY_ONE_DAY_MS);
354                     jsonWriter.endObject();
355                 }
356                 atomicFile.finishWrite(fos);
357             } catch (IOException e) {
358                 Log.e(TAG, "Unable to write to disk", e);
359                 atomicFile.failWrite(fos);
360             }
361         }
362     }
363 
364     /**
365      * Reads a previously stored location and attempts to inject it into the location manager proxy.
366      */
loadLocation()367     private void loadLocation() {
368         Location location = readLocationFromCacheFile();
369         logd("Read location from timestamp %s", location.getTime());
370         long currentTime = System.currentTimeMillis();
371         if (location.getTime() + TTL_THIRTY_DAYS_MS < currentTime) {
372             logd("Location expired.");
373             deleteCacheFile();
374         } else {
375             location.setTime(currentTime);
376             long elapsedTime = SystemClock.elapsedRealtimeNanos();
377             location.setElapsedRealtimeNanos(elapsedTime);
378             if (location.isComplete()) {
379                 injectLocation(location, 1);
380             }
381         }
382     }
383 
readLocationFromCacheFile()384     private Location readLocationFromCacheFile() {
385         Location location = new Location((String) null);
386         File file = getLocationCacheFile();
387         AtomicFile atomicFile = new AtomicFile(file);
388         try (FileInputStream fis = atomicFile.openRead()) {
389             JsonReader reader = new JsonReader(new InputStreamReader(fis, "UTF-8"));
390             reader.beginObject();
391             while (reader.hasNext()) {
392                 String name = reader.nextName();
393                 switch (name) {
394                     case PROVIDER:
395                         location.setProvider(reader.nextString());
396                         break;
397                     case LATITUDE:
398                         location.setLatitude(reader.nextDouble());
399                         break;
400                     case LONGITUDE:
401                         location.setLongitude(reader.nextDouble());
402                         break;
403                     case ALTITUDE:
404                         location.setAltitude(reader.nextDouble());
405                         break;
406                     case SPEED:
407                         location.setSpeed((float) reader.nextDouble());
408                         break;
409                     case BEARING:
410                         location.setBearing((float) reader.nextDouble());
411                         break;
412                     case ACCURACY:
413                         location.setAccuracy((float) reader.nextDouble());
414                         break;
415                     case VERTICAL_ACCURACY:
416                         location.setVerticalAccuracyMeters((float) reader.nextDouble());
417                         break;
418                     case SPEED_ACCURACY:
419                         location.setSpeedAccuracyMetersPerSecond((float) reader.nextDouble());
420                         break;
421                     case BEARING_ACCURACY:
422                         location.setBearingAccuracyDegrees((float) reader.nextDouble());
423                         break;
424                     case IS_FROM_MOCK_PROVIDER:
425                         location.setIsFromMockProvider(reader.nextBoolean());
426                         break;
427                     case CAPTURE_TIME:
428                         location.setTime(reader.nextLong());
429                         break;
430                     default:
431                         Log.w(TAG, String.format("Unrecognized key: %s", name));
432                         reader.skipValue();
433                 }
434             }
435             reader.endObject();
436         } catch (FileNotFoundException e) {
437             logd("Location cache file not found: %s", file);
438         } catch (IOException e) {
439             Log.e(TAG, "Unable to read from disk", e);
440         } catch (NumberFormatException | IllegalStateException e) {
441             Log.e(TAG, "Unexpected format", e);
442         }
443         return location;
444     }
445 
deleteCacheFile()446     private void deleteCacheFile() {
447         File file = getLocationCacheFile();
448         boolean deleted = file.delete();
449         if (deleted) {
450             logd("Successfully deleted cache file at %s", file);
451         } else {
452             logd("Failed to delete cache file at %s", file);
453         }
454     }
455 
456     /**
457      * Attempts to inject the location multiple times in case the LocationManager was not fully
458      * initialized or has not updated its handle to the current user yet.
459      */
injectLocation(Location location, int attemptCount)460     private void injectLocation(Location location, int attemptCount) {
461         boolean success = false;
462         synchronized (mLocationManagerProxyLock) {
463             if (mILocationManagerProxy == null) {
464                 logd("Null location manager proxy.");
465             } else {
466                 try {
467                     success = mILocationManagerProxy.injectLocation(location);
468                 } catch (RemoteException e) {
469                     Log.e(TAG, "RemoteException from ILocationManagerProxy", e);
470                 }
471             }
472         }
473         if (success) {
474             logd("Successfully injected stored location on attempt %s.", attemptCount);
475             return;
476         } else if (attemptCount <= MAX_LOCATION_INJECTION_ATTEMPTS) {
477             logd("Failed to inject stored location on attempt %s.", attemptCount);
478             asyncOperation(() -> {
479                 injectLocation(location, attemptCount + 1);
480             }, 200 * attemptCount);
481         } else {
482             logd("No location injected.");
483         }
484     }
485 
getLocationCacheFile()486     private File getLocationCacheFile() {
487         SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
488         return new File(systemInterface.getSystemCarDir(), FILENAME);
489     }
490 
491     @VisibleForTesting
asyncOperation(Runnable operation)492     void asyncOperation(Runnable operation) {
493         asyncOperation(operation, 0);
494     }
495 
asyncOperation(Runnable operation, long delayMillis)496     private void asyncOperation(Runnable operation, long delayMillis) {
497         mHandler.postDelayed(() -> operation.run(), delayMillis);
498     }
499 
logd(String msg, Object... vals)500     private static void logd(String msg, Object... vals) {
501         // Disable logs here if they become too spammy.
502         Log.d(TAG, String.format(msg, vals));
503     }
504 }
505