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