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