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.IndentingPrintWriter;
41 import android.util.JsonReader;
42 import android.util.JsonWriter;
43 import android.util.Slog;
44 
45 import com.android.car.systeminterface.SystemInterface;
46 import com.android.internal.annotations.VisibleForTesting;
47 
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.FileNotFoundException;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStreamReader;
54 import java.io.OutputStreamWriter;
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 = CarLog.tagFor(CarLocationService.class);
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                             Slog.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(IndentingPrintWriter writer)211     public void dump(IndentingPrintWriter writer) {
212         writer.println(TAG);
213         mPerUserCarServiceHelper.dump(writer);
214         writer.printf("Context: %s\n", mContext);
215         writer.printf("MAX_LOCATION_INJECTION_ATTEMPTS: %d\n", MAX_LOCATION_INJECTION_ATTEMPTS);
216     }
217 
218     @Override
onStateChanged(int state, CompletableFuture<Void> future)219     public void onStateChanged(int state, CompletableFuture<Void> future) {
220         logd("onStateChanged: %s", state);
221         switch (state) {
222             case CarPowerStateListener.SHUTDOWN_PREPARE:
223                 asyncOperation(() -> {
224                     storeLocation();
225                     // Notify the CarPowerManager that it may proceed to shutdown or suspend.
226                     if (future != null) {
227                         future.complete(null);
228                     }
229                 });
230                 break;
231             case CarPowerStateListener.SUSPEND_EXIT:
232                 if (mCarDrivingStateService != null) {
233                     CarDrivingStateEvent event = mCarDrivingStateService.getCurrentDrivingState();
234                     if (event != null
235                             && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
236                         deleteCacheFile();
237                     } else {
238                         logd("Registering to receive driving state.");
239                         mCarDrivingStateService.registerDrivingStateChangeListener(
240                                 mICarDrivingStateChangeEventListener);
241                     }
242                 }
243                 if (future != null) {
244                     future.complete(null);
245                 }
246             default:
247                 // This service does not need to do any work for these events but should still
248                 // notify the CarPowerManager that it may proceed.
249                 if (future != null) {
250                     future.complete(null);
251                 }
252                 break;
253         }
254     }
255 
256     @Override
onReceive(Context context, Intent intent)257     public void onReceive(Context context, Intent intent) {
258         logd("onReceive %s", intent);
259         // If the system user is headless but the current user is still the system user, then we
260         // should not delete the location cache file due to missing location permissions.
261         if (isCurrentUserHeadlessSystemUser()) {
262             logd("Current user is headless system user.");
263             return;
264         }
265         synchronized (mLocationManagerProxyLock) {
266             if (mILocationManagerProxy == null) {
267                 logd("Null location manager.");
268                 return;
269             }
270             String action = intent.getAction();
271             try {
272                 if (action == LocationManager.MODE_CHANGED_ACTION) {
273                     boolean locationEnabled = mILocationManagerProxy.isLocationEnabled();
274                     logd("isLocationEnabled(): %s", locationEnabled);
275                     if (!locationEnabled) {
276                         deleteCacheFile();
277                     }
278                 } else {
279                     logd("Unexpected intent.");
280                 }
281             } catch (RemoteException e) {
282                 Slog.e(TAG, "RemoteException from ILocationManagerProxy", e);
283             }
284         }
285     }
286 
287     /** Tells whether the current foreground user is the headless system user. */
isCurrentUserHeadlessSystemUser()288     private boolean isCurrentUserHeadlessSystemUser() {
289         int currentUserId = ActivityManager.getCurrentUser();
290         return UserManager.isHeadlessSystemUserMode()
291                 && currentUserId == UserHandle.USER_SYSTEM;
292     }
293 
294     /**
295      * Gets the last known location from the location manager proxy and store it in a file.
296      */
storeLocation()297     private void storeLocation() {
298         Location location = null;
299         synchronized (mLocationManagerProxyLock) {
300             if (mILocationManagerProxy == null) {
301                 logd("Null location manager proxy.");
302                 return;
303             }
304             try {
305                 location = mILocationManagerProxy.getLastKnownLocation(
306                         LocationManager.GPS_PROVIDER);
307             } catch (RemoteException e) {
308                 Slog.e(TAG, "RemoteException from ILocationManagerProxy", e);
309             }
310         }
311         if (location == null) {
312             logd("Not storing null location");
313         } else {
314             logd("Storing location");
315             AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
316             FileOutputStream fos = null;
317             try {
318                 fos = atomicFile.startWrite();
319                 try (JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(fos, "UTF-8"))) {
320                     jsonWriter.beginObject();
321                     jsonWriter.name(PROVIDER).value(location.getProvider());
322                     jsonWriter.name(LATITUDE).value(location.getLatitude());
323                     jsonWriter.name(LONGITUDE).value(location.getLongitude());
324                     if (location.hasAltitude()) {
325                         jsonWriter.name(ALTITUDE).value(location.getAltitude());
326                     }
327                     if (location.hasSpeed()) {
328                         jsonWriter.name(SPEED).value(location.getSpeed());
329                     }
330                     if (location.hasBearing()) {
331                         jsonWriter.name(BEARING).value(location.getBearing());
332                     }
333                     if (location.hasAccuracy()) {
334                         jsonWriter.name(ACCURACY).value(location.getAccuracy());
335                     }
336                     if (location.hasVerticalAccuracy()) {
337                         jsonWriter.name(VERTICAL_ACCURACY).value(
338                                 location.getVerticalAccuracyMeters());
339                     }
340                     if (location.hasSpeedAccuracy()) {
341                         jsonWriter.name(SPEED_ACCURACY).value(
342                                 location.getSpeedAccuracyMetersPerSecond());
343                     }
344                     if (location.hasBearingAccuracy()) {
345                         jsonWriter.name(BEARING_ACCURACY).value(
346                                 location.getBearingAccuracyDegrees());
347                     }
348                     if (location.isFromMockProvider()) {
349                         jsonWriter.name(IS_FROM_MOCK_PROVIDER).value(true);
350                     }
351                     long currentTime = location.getTime();
352                     // Round the time down to only be accurate within one day.
353                     jsonWriter.name(CAPTURE_TIME).value(
354                             currentTime - currentTime % GRANULARITY_ONE_DAY_MS);
355                     jsonWriter.endObject();
356                 }
357                 atomicFile.finishWrite(fos);
358             } catch (IOException e) {
359                 Slog.e(TAG, "Unable to write to disk", e);
360                 atomicFile.failWrite(fos);
361             }
362         }
363     }
364 
365     /**
366      * Reads a previously stored location and attempts to inject it into the location manager proxy.
367      */
loadLocation()368     private void loadLocation() {
369         Location location = readLocationFromCacheFile();
370         logd("Read location from timestamp %s", location.getTime());
371         long currentTime = System.currentTimeMillis();
372         if (location.getTime() + TTL_THIRTY_DAYS_MS < currentTime) {
373             logd("Location expired.");
374             deleteCacheFile();
375         } else {
376             location.setTime(currentTime);
377             long elapsedTime = SystemClock.elapsedRealtimeNanos();
378             location.setElapsedRealtimeNanos(elapsedTime);
379             if (location.isComplete()) {
380                 injectLocation(location, 1);
381             }
382         }
383     }
384 
readLocationFromCacheFile()385     private Location readLocationFromCacheFile() {
386         Location location = new Location((String) null);
387         File file = getLocationCacheFile();
388         AtomicFile atomicFile = new AtomicFile(file);
389         try (FileInputStream fis = atomicFile.openRead()) {
390             JsonReader reader = new JsonReader(new InputStreamReader(fis, "UTF-8"));
391             reader.beginObject();
392             while (reader.hasNext()) {
393                 String name = reader.nextName();
394                 switch (name) {
395                     case PROVIDER:
396                         location.setProvider(reader.nextString());
397                         break;
398                     case LATITUDE:
399                         location.setLatitude(reader.nextDouble());
400                         break;
401                     case LONGITUDE:
402                         location.setLongitude(reader.nextDouble());
403                         break;
404                     case ALTITUDE:
405                         location.setAltitude(reader.nextDouble());
406                         break;
407                     case SPEED:
408                         location.setSpeed((float) reader.nextDouble());
409                         break;
410                     case BEARING:
411                         location.setBearing((float) reader.nextDouble());
412                         break;
413                     case ACCURACY:
414                         location.setAccuracy((float) reader.nextDouble());
415                         break;
416                     case VERTICAL_ACCURACY:
417                         location.setVerticalAccuracyMeters((float) reader.nextDouble());
418                         break;
419                     case SPEED_ACCURACY:
420                         location.setSpeedAccuracyMetersPerSecond((float) reader.nextDouble());
421                         break;
422                     case BEARING_ACCURACY:
423                         location.setBearingAccuracyDegrees((float) reader.nextDouble());
424                         break;
425                     case IS_FROM_MOCK_PROVIDER:
426                         location.setIsFromMockProvider(reader.nextBoolean());
427                         break;
428                     case CAPTURE_TIME:
429                         location.setTime(reader.nextLong());
430                         break;
431                     default:
432                         Slog.w(TAG, "Unrecognized key: " + name);
433                         reader.skipValue();
434                 }
435             }
436             reader.endObject();
437         } catch (FileNotFoundException e) {
438             logd("Location cache file not found: %s", file);
439         } catch (IOException e) {
440             Slog.e(TAG, "Unable to read from disk", e);
441         } catch (NumberFormatException | IllegalStateException e) {
442             Slog.e(TAG, "Unexpected format", e);
443         }
444         return location;
445     }
446 
deleteCacheFile()447     private void deleteCacheFile() {
448         File file = getLocationCacheFile();
449         boolean deleted = file.delete();
450         if (deleted) {
451             logd("Successfully deleted cache file at %s", file);
452         } else {
453             logd("Failed to delete cache file at %s", file);
454         }
455     }
456 
457     /**
458      * Attempts to inject the location multiple times in case the LocationManager was not fully
459      * initialized or has not updated its handle to the current user yet.
460      */
injectLocation(Location location, int attemptCount)461     private void injectLocation(Location location, int attemptCount) {
462         boolean success = false;
463         synchronized (mLocationManagerProxyLock) {
464             if (mILocationManagerProxy == null) {
465                 logd("Null location manager proxy.");
466             } else {
467                 try {
468                     success = mILocationManagerProxy.injectLocation(location);
469                 } catch (RemoteException e) {
470                     Slog.e(TAG, "RemoteException from ILocationManagerProxy", e);
471                 }
472             }
473         }
474         if (success) {
475             logd("Successfully injected stored location on attempt %s.", attemptCount);
476             return;
477         } else if (attemptCount <= MAX_LOCATION_INJECTION_ATTEMPTS) {
478             logd("Failed to inject stored location on attempt %s.", attemptCount);
479             asyncOperation(() -> {
480                 injectLocation(location, attemptCount + 1);
481             }, 200 * attemptCount);
482         } else {
483             logd("No location injected.");
484         }
485     }
486 
getLocationCacheFile()487     private File getLocationCacheFile() {
488         SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
489         return new File(systemInterface.getSystemCarDir(), FILENAME);
490     }
491 
492     @VisibleForTesting
asyncOperation(Runnable operation)493     void asyncOperation(Runnable operation) {
494         asyncOperation(operation, 0);
495     }
496 
asyncOperation(Runnable operation, long delayMillis)497     private void asyncOperation(Runnable operation, long delayMillis) {
498         mHandler.postDelayed(() -> operation.run(), delayMillis);
499     }
500 
logd(String msg, Object... vals)501     private static void logd(String msg, Object... vals) {
502         // Disable logs here if they become too spammy.
503         Slog.d(TAG, String.format(msg, vals));
504     }
505 }
506