blob: 43d4a46501a53d76d9f1afcb498432a9e7fd0da7 [file] [log] [blame]
Gregory Clarkd8136062017-12-11 14:27:53 -08001/*
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
17package com.android.car;
18
Serik Beketayev68920542018-09-06 13:29:58 -070019import android.car.hardware.power.CarPowerManager;
20import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
Jim Kayee5133162019-04-22 12:50:27 -070021import android.car.hardware.power.CarPowerManager.CarPowerStateListenerWithCompletion;
Ying Zheng9fc99402018-09-19 14:23:59 -070022import android.car.userlib.CarUserManagerHelper;
Gregory Clarke70eb5b2018-02-07 18:29:24 -080023import android.content.BroadcastReceiver;
Gregory Clarkd8136062017-12-11 14:27:53 -080024import android.content.Context;
Gregory Clarke70eb5b2018-02-07 18:29:24 -080025import android.content.Intent;
26import android.content.IntentFilter;
Gregory Clarkd8136062017-12-11 14:27:53 -080027import android.location.Location;
28import android.location.LocationManager;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080029import android.os.Handler;
30import android.os.HandlerThread;
Gregory Clark756ed812018-02-06 18:19:27 -080031import android.os.SystemClock;
Gregory Clarka63ba022018-06-07 16:42:12 -070032import android.os.UserHandle;
Gregory Clarkd8136062017-12-11 14:27:53 -080033import android.util.AtomicFile;
34import android.util.JsonReader;
35import android.util.JsonWriter;
36import android.util.Log;
37
Gregory Clarka440e812019-02-14 16:05:51 -080038import com.android.car.systeminterface.SystemInterface;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080039import com.android.internal.annotations.VisibleForTesting;
40
Gregory Clarka440e812019-02-14 16:05:51 -080041import java.io.File;
Gregory Clarkd8136062017-12-11 14:27:53 -080042import java.io.FileInputStream;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080043import java.io.FileNotFoundException;
Gregory Clarkd8136062017-12-11 14:27:53 -080044import java.io.FileOutputStream;
45import java.io.IOException;
46import java.io.InputStreamReader;
47import java.io.OutputStreamWriter;
48import java.io.PrintWriter;
Serik Beketayev68920542018-09-06 13:29:58 -070049import java.util.concurrent.CompletableFuture;
Gregory Clarkd8136062017-12-11 14:27:53 -080050
51/**
52 * This service stores the last known location from {@link LocationManager} when a car is parked
53 * and restores the location when the car is powered on.
Gregory Clarkd8136062017-12-11 14:27:53 -080054 */
Serik Beketayev68920542018-09-06 13:29:58 -070055public class CarLocationService extends BroadcastReceiver implements
Jim Kayee5133162019-04-22 12:50:27 -070056 CarServiceBase, CarPowerStateListenerWithCompletion {
Gregory Clark67259c22018-03-09 15:30:21 -080057 private static final String TAG = "CarLocationService";
58 private static final String FILENAME = "location_cache.json";
Gregory Clarka440e812019-02-14 16:05:51 -080059 private static final boolean DBG = true;
Gregory Clark67259c22018-03-09 15:30:21 -080060 // The accuracy for the stored timestamp
61 private static final long GRANULARITY_ONE_DAY_MS = 24 * 60 * 60 * 1000L;
62 // The time-to-live for the cached location
63 private static final long TTL_THIRTY_DAYS_MS = 30 * GRANULARITY_ONE_DAY_MS;
Gregory Clarka63ba022018-06-07 16:42:12 -070064 // The maximum number of times to try injecting a location
65 private static final int MAX_LOCATION_INJECTION_ATTEMPTS = 10;
Gregory Clarkd8136062017-12-11 14:27:53 -080066
Gregory Clark3f9b56d2018-01-31 13:02:41 -080067 // Used internally for mHandlerThread synchronization
Gregory Clarkd8136062017-12-11 14:27:53 -080068 private final Object mLock = new Object();
69
70 private final Context mContext;
Gregory Clarka63ba022018-06-07 16:42:12 -070071 private final CarUserManagerHelper mCarUserManagerHelper;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080072 private int mTaskCount = 0;
73 private HandlerThread mHandlerThread;
74 private Handler mHandler;
Serik Beketayev68920542018-09-06 13:29:58 -070075 private CarPowerManager mCarPowerManager;
Gregory Clarkd8136062017-12-11 14:27:53 -080076
Serik Beketayev68920542018-09-06 13:29:58 -070077 public CarLocationService(
78 Context context,
Serik Beketayev68920542018-09-06 13:29:58 -070079 CarUserManagerHelper carUserManagerHelper) {
Gregory Clarkd8136062017-12-11 14:27:53 -080080 logd("constructed");
81 mContext = context;
Gregory Clarka63ba022018-06-07 16:42:12 -070082 mCarUserManagerHelper = carUserManagerHelper;
Gregory Clarkd8136062017-12-11 14:27:53 -080083 }
84
85 @Override
86 public void init() {
87 logd("init");
Gregory Clarke70eb5b2018-02-07 18:29:24 -080088 IntentFilter filter = new IntentFilter();
Gregory Clarka63ba022018-06-07 16:42:12 -070089 filter.addAction(Intent.ACTION_USER_SWITCHED);
Gregory Clark29b7f0f2018-02-16 17:53:40 -080090 filter.addAction(LocationManager.MODE_CHANGED_ACTION);
Soonil Nagarkarc9563162019-03-04 18:53:39 -080091 filter.addAction(LocationManager.PROVIDERS_CHANGED_ACTION);
Gregory Clarke70eb5b2018-02-07 18:29:24 -080092 mContext.registerReceiver(this, filter);
Keun young Parka5fa4782019-04-16 18:56:27 -070093 mCarPowerManager = CarLocalServices.createCarPowerManager(mContext);
94 if (mCarPowerManager != null) { // null case happens for testing.
Jim Kayee5133162019-04-22 12:50:27 -070095 mCarPowerManager.setListenerWithCompletion(CarLocationService.this);
Keun young Parka5fa4782019-04-16 18:56:27 -070096 }
Gregory Clarkd8136062017-12-11 14:27:53 -080097 }
98
99 @Override
100 public void release() {
101 logd("release");
Keun young Parka5fa4782019-04-16 18:56:27 -0700102 if (mCarPowerManager != null) {
103 mCarPowerManager.clearListener();
104 }
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800105 mContext.unregisterReceiver(this);
Gregory Clarkd8136062017-12-11 14:27:53 -0800106 }
107
108 @Override
109 public void dump(PrintWriter writer) {
110 writer.println(TAG);
111 writer.println("Context: " + mContext);
Gregory Clarka63ba022018-06-07 16:42:12 -0700112 writer.println("MAX_LOCATION_INJECTION_ATTEMPTS: " + MAX_LOCATION_INJECTION_ATTEMPTS);
Gregory Clarkd8136062017-12-11 14:27:53 -0800113 }
114
115 @Override
Serik Beketayev68920542018-09-06 13:29:58 -0700116 public void onStateChanged(int state, CompletableFuture<Void> future) {
Steve Paik07db5ed2018-09-24 16:48:52 -0700117 logd("onStateChanged: " + state);
Serik Beketayev68920542018-09-06 13:29:58 -0700118 switch (state) {
Steve Paik07db5ed2018-09-24 16:48:52 -0700119 case CarPowerStateListener.SHUTDOWN_PREPARE:
Gregory Clark5b9a9cd2018-09-10 13:21:22 -0700120 asyncOperation(() -> {
121 storeLocation();
122 // Notify the CarPowerManager that it may proceed to shutdown or suspend.
Gregory Clark71ed6722018-09-24 13:10:16 -0700123 if (future != null) {
124 future.complete(null);
125 }
Gregory Clark5b9a9cd2018-09-10 13:21:22 -0700126 });
Serik Beketayev68920542018-09-06 13:29:58 -0700127 break;
Gregory Clark63a4ad22019-05-23 18:36:33 -0700128 case CarPowerStateListener.SUSPEND_EXIT:
129 deleteCacheFile();
130 if (future != null) {
131 future.complete(null);
132 }
Steve Paik07db5ed2018-09-24 16:48:52 -0700133 default:
Gregory Clark5b9a9cd2018-09-10 13:21:22 -0700134 // This service does not need to do any work for these events but should still
135 // notify the CarPowerManager that it may proceed.
Gregory Clark71ed6722018-09-24 13:10:16 -0700136 if (future != null) {
137 future.complete(null);
138 }
Serik Beketayev68920542018-09-06 13:29:58 -0700139 break;
140 }
Gregory Clark26fa6012018-03-14 18:38:56 -0700141 }
142
Gregory Clark26fa6012018-03-14 18:38:56 -0700143 @Override
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800144 public void onReceive(Context context, Intent intent) {
Gregory Clark26fa6012018-03-14 18:38:56 -0700145 logd("onReceive " + intent);
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800146 String action = intent.getAction();
Gregory Clark60575502019-04-08 22:42:40 -0700147 if (action == Intent.ACTION_USER_SWITCHED) {
Gregory Clarka63ba022018-06-07 16:42:12 -0700148 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
149 logd("USER_SWITCHED: " + userHandle);
150 if (mCarUserManagerHelper.isHeadlessSystemUser()
151 && userHandle > UserHandle.USER_SYSTEM) {
152 asyncOperation(() -> loadLocation());
153 }
154 } else if (action == LocationManager.MODE_CHANGED_ACTION
155 && shouldCheckLocationPermissions()) {
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800156 LocationManager locationManager =
157 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
Gregory Clarka63ba022018-06-07 16:42:12 -0700158 boolean locationEnabled = locationManager.isLocationEnabled();
159 logd("isLocationEnabled(): " + locationEnabled);
160 if (!locationEnabled) {
Gregory Clark63a4ad22019-05-23 18:36:33 -0700161 deleteCacheFile();
Gregory Clarka63ba022018-06-07 16:42:12 -0700162 }
Soonil Nagarkarc9563162019-03-04 18:53:39 -0800163 } else if (action == LocationManager.PROVIDERS_CHANGED_ACTION
Gregory Clarka63ba022018-06-07 16:42:12 -0700164 && shouldCheckLocationPermissions()) {
165 LocationManager locationManager =
166 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
167 boolean gpsEnabled =
168 locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
169 logd("isProviderEnabled('gps'): " + gpsEnabled);
170 if (!gpsEnabled) {
Gregory Clark63a4ad22019-05-23 18:36:33 -0700171 deleteCacheFile();
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800172 }
173 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800174 }
175
Gregory Clarka63ba022018-06-07 16:42:12 -0700176 /**
177 * Tells whether or not we should check location permissions for the sake of deleting the
178 * location cache file when permissions are lacking. If the system user is headless but the
179 * current user is still the system user, then we should not respond to a lack of location
180 * permissions.
181 */
182 private boolean shouldCheckLocationPermissions() {
183 return !(mCarUserManagerHelper.isHeadlessSystemUser()
184 && mCarUserManagerHelper.isCurrentProcessSystemUser());
185 }
186
187 /**
188 * Gets the last known location from the LocationManager and store it in a file.
189 */
Gregory Clarkd8136062017-12-11 14:27:53 -0800190 private void storeLocation() {
191 LocationManager locationManager =
192 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
193 Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
194 if (location == null) {
195 logd("Not storing null location");
196 } else {
197 logd("Storing location: " + location);
Gregory Clarka440e812019-02-14 16:05:51 -0800198 AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
Gregory Clarkd8136062017-12-11 14:27:53 -0800199 FileOutputStream fos = null;
200 try {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800201 fos = atomicFile.startWrite();
Gregory Clarka63ba022018-06-07 16:42:12 -0700202 try (JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(fos, "UTF-8"))) {
203 jsonWriter.beginObject();
204 jsonWriter.name("provider").value(location.getProvider());
205 jsonWriter.name("latitude").value(location.getLatitude());
206 jsonWriter.name("longitude").value(location.getLongitude());
207 if (location.hasAltitude()) {
208 jsonWriter.name("altitude").value(location.getAltitude());
209 }
210 if (location.hasSpeed()) {
211 jsonWriter.name("speed").value(location.getSpeed());
212 }
213 if (location.hasBearing()) {
214 jsonWriter.name("bearing").value(location.getBearing());
215 }
216 if (location.hasAccuracy()) {
217 jsonWriter.name("accuracy").value(location.getAccuracy());
218 }
219 if (location.hasVerticalAccuracy()) {
220 jsonWriter.name("verticalAccuracy").value(
221 location.getVerticalAccuracyMeters());
222 }
223 if (location.hasSpeedAccuracy()) {
224 jsonWriter.name("speedAccuracy").value(
225 location.getSpeedAccuracyMetersPerSecond());
226 }
227 if (location.hasBearingAccuracy()) {
228 jsonWriter.name("bearingAccuracy").value(
229 location.getBearingAccuracyDegrees());
230 }
231 if (location.isFromMockProvider()) {
232 jsonWriter.name("isFromMockProvider").value(true);
233 }
234 long currentTime = location.getTime();
235 // Round the time down to only be accurate within one day.
236 jsonWriter.name("captureTime").value(
237 currentTime - currentTime % GRANULARITY_ONE_DAY_MS);
238 jsonWriter.endObject();
Gregory Clarkd8136062017-12-11 14:27:53 -0800239 }
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800240 atomicFile.finishWrite(fos);
Gregory Clarkd8136062017-12-11 14:27:53 -0800241 } catch (IOException e) {
242 Log.e(TAG, "Unable to write to disk", e);
243 atomicFile.failWrite(fos);
244 }
245 }
246 }
247
Gregory Clarka63ba022018-06-07 16:42:12 -0700248 /**
249 * Reads a previously stored location and attempts to inject it into the LocationManager.
250 */
Gregory Clarkd8136062017-12-11 14:27:53 -0800251 private void loadLocation() {
Gregory Clark67259c22018-03-09 15:30:21 -0800252 Location location = readLocationFromCacheFile();
Gregory Clarka440e812019-02-14 16:05:51 -0800253 logd("Read location from timestamp " + location.getTime());
Gregory Clark67259c22018-03-09 15:30:21 -0800254 long currentTime = System.currentTimeMillis();
255 if (location.getTime() + TTL_THIRTY_DAYS_MS < currentTime) {
256 logd("Location expired.");
Gregory Clark63a4ad22019-05-23 18:36:33 -0700257 deleteCacheFile();
Gregory Clark67259c22018-03-09 15:30:21 -0800258 } else {
259 location.setTime(currentTime);
260 long elapsedTime = SystemClock.elapsedRealtimeNanos();
261 location.setElapsedRealtimeNanos(elapsedTime);
262 if (location.isComplete()) {
Gregory Clarka63ba022018-06-07 16:42:12 -0700263 injectLocation(location, 1);
Gregory Clark67259c22018-03-09 15:30:21 -0800264 }
265 }
266 }
267
268 private Location readLocationFromCacheFile() {
Gregory Clarkd8136062017-12-11 14:27:53 -0800269 Location location = new Location((String) null);
Gregory Clarka440e812019-02-14 16:05:51 -0800270 AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
Gregory Clarka63ba022018-06-07 16:42:12 -0700271 try (FileInputStream fis = atomicFile.openRead()) {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800272 JsonReader reader = new JsonReader(new InputStreamReader(fis, "UTF-8"));
273 reader.beginObject();
274 while (reader.hasNext()) {
275 String name = reader.nextName();
276 if (name.equals("provider")) {
277 location.setProvider(reader.nextString());
278 } else if (name.equals("latitude")) {
279 location.setLatitude(reader.nextDouble());
280 } else if (name.equals("longitude")) {
281 location.setLongitude(reader.nextDouble());
282 } else if (name.equals("altitude")) {
283 location.setAltitude(reader.nextDouble());
284 } else if (name.equals("speed")) {
285 location.setSpeed((float) reader.nextDouble());
286 } else if (name.equals("bearing")) {
287 location.setBearing((float) reader.nextDouble());
288 } else if (name.equals("accuracy")) {
289 location.setAccuracy((float) reader.nextDouble());
290 } else if (name.equals("verticalAccuracy")) {
291 location.setVerticalAccuracyMeters((float) reader.nextDouble());
292 } else if (name.equals("speedAccuracy")) {
293 location.setSpeedAccuracyMetersPerSecond((float) reader.nextDouble());
294 } else if (name.equals("bearingAccuracy")) {
295 location.setBearingAccuracyDegrees((float) reader.nextDouble());
296 } else if (name.equals("isFromMockProvider")) {
297 location.setIsFromMockProvider(reader.nextBoolean());
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800298 } else if (name.equals("captureTime")) {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800299 location.setTime(reader.nextLong());
300 } else {
301 reader.skipValue();
Gregory Clarkd8136062017-12-11 14:27:53 -0800302 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800303 }
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800304 reader.endObject();
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800305 } catch (FileNotFoundException e) {
306 Log.d(TAG, "Location cache file not found.");
Gregory Clarkd8136062017-12-11 14:27:53 -0800307 } catch (IOException e) {
308 Log.e(TAG, "Unable to read from disk", e);
309 } catch (NumberFormatException | IllegalStateException e) {
310 Log.e(TAG, "Unexpected format", e);
311 }
Gregory Clark67259c22018-03-09 15:30:21 -0800312 return location;
Gregory Clarkd8136062017-12-11 14:27:53 -0800313 }
314
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800315 private void deleteCacheFile() {
Gregory Clarka440e812019-02-14 16:05:51 -0800316 boolean deleted = getLocationCacheFile().delete();
317 logd("Deleted cache file: " + deleted);
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800318 }
319
Gregory Clarka63ba022018-06-07 16:42:12 -0700320 /**
321 * Attempts to inject the location multiple times in case the LocationManager was not fully
322 * initialized or has not updated its handle to the current user yet.
323 */
324 private void injectLocation(Location location, int attemptCount) {
325 LocationManager locationManager =
326 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
327 boolean success = locationManager.injectLocation(location);
328 logd("Injected location " + location + " with result " + success + " on attempt "
329 + attemptCount);
330 if (success) {
331 return;
332 } else if (attemptCount <= MAX_LOCATION_INJECTION_ATTEMPTS) {
333 asyncOperation(() -> {
334 injectLocation(location, attemptCount + 1);
335 }, 200 * attemptCount);
336 } else {
337 logd("No location injected.");
338 }
339 }
340
Gregory Clarka440e812019-02-14 16:05:51 -0800341 private File getLocationCacheFile() {
342 SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
343 File file = new File(systemInterface.getSystemCarDir(), FILENAME);
344 logd("File: " + file);
345 return file;
346 }
347
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800348 @VisibleForTesting
349 void asyncOperation(Runnable operation) {
Gregory Clarka63ba022018-06-07 16:42:12 -0700350 asyncOperation(operation, 0);
351 }
352
353 private void asyncOperation(Runnable operation, long delayMillis) {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800354 synchronized (mLock) {
355 // Create a new HandlerThread if this is the first task to queue.
356 if (++mTaskCount == 1) {
357 mHandlerThread = new HandlerThread("CarLocationServiceThread");
358 mHandlerThread.start();
359 mHandler = new Handler(mHandlerThread.getLooper());
360 }
361 }
Gregory Clarka63ba022018-06-07 16:42:12 -0700362 mHandler.postDelayed(() -> {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800363 try {
364 operation.run();
365 } finally {
366 synchronized (mLock) {
367 // Quit the thread when the task queue is empty.
368 if (--mTaskCount == 0) {
369 mHandler.getLooper().quit();
370 mHandler = null;
371 mHandlerThread = null;
372 }
373 }
374 }
Gregory Clarka63ba022018-06-07 16:42:12 -0700375 }, delayMillis);
Gregory Clarkd8136062017-12-11 14:27:53 -0800376 }
377
378 private static void logd(String msg) {
379 if (DBG) {
380 Log.d(TAG, msg);
381 }
382 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800383}