blob: 2f37e617f1a5476eb2a190c62dcaa2433baf0c2b [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
Gregory Clark55620c12019-06-03 17:42:58 -070019import android.app.ActivityManager;
20import android.car.ICarUserService;
21import android.car.ILocationManagerProxy;
Gregory Clark18a0ef92019-05-23 20:00:49 -070022import android.car.drivingstate.CarDrivingStateEvent;
23import android.car.drivingstate.ICarDrivingStateChangeListener;
Serik Beketayev68920542018-09-06 13:29:58 -070024import android.car.hardware.power.CarPowerManager;
25import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
Jim Kayee5133162019-04-22 12:50:27 -070026import android.car.hardware.power.CarPowerManager.CarPowerStateListenerWithCompletion;
Ying Zheng9fc99402018-09-19 14:23:59 -070027import android.car.userlib.CarUserManagerHelper;
Gregory Clarke70eb5b2018-02-07 18:29:24 -080028import android.content.BroadcastReceiver;
Gregory Clarkd8136062017-12-11 14:27:53 -080029import android.content.Context;
Gregory Clarke70eb5b2018-02-07 18:29:24 -080030import android.content.Intent;
31import android.content.IntentFilter;
Gregory Clarkd8136062017-12-11 14:27:53 -080032import android.location.Location;
33import android.location.LocationManager;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080034import android.os.Handler;
35import android.os.HandlerThread;
Gregory Clark55620c12019-06-03 17:42:58 -070036import android.os.RemoteException;
Gregory Clark756ed812018-02-06 18:19:27 -080037import android.os.SystemClock;
Gregory Clarka63ba022018-06-07 16:42:12 -070038import android.os.UserHandle;
Gregory Clarkd8136062017-12-11 14:27:53 -080039import android.util.AtomicFile;
40import android.util.JsonReader;
41import android.util.JsonWriter;
42import android.util.Log;
43
Gregory Clarka440e812019-02-14 16:05:51 -080044import com.android.car.systeminterface.SystemInterface;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080045import com.android.internal.annotations.VisibleForTesting;
46
Gregory Clarka440e812019-02-14 16:05:51 -080047import java.io.File;
Gregory Clarkd8136062017-12-11 14:27:53 -080048import java.io.FileInputStream;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080049import java.io.FileNotFoundException;
Gregory Clarkd8136062017-12-11 14:27:53 -080050import java.io.FileOutputStream;
51import java.io.IOException;
52import java.io.InputStreamReader;
53import java.io.OutputStreamWriter;
54import java.io.PrintWriter;
Serik Beketayev68920542018-09-06 13:29:58 -070055import java.util.concurrent.CompletableFuture;
Gregory Clarkd8136062017-12-11 14:27:53 -080056
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.
Gregory Clarkd8136062017-12-11 14:27:53 -080060 */
Gregory Clark18a0ef92019-05-23 20:00:49 -070061public class CarLocationService extends BroadcastReceiver implements CarServiceBase,
62 CarPowerStateListenerWithCompletion {
Gregory Clark67259c22018-03-09 15:30:21 -080063 private static final String TAG = "CarLocationService";
64 private static final String FILENAME = "location_cache.json";
Gregory Clarka440e812019-02-14 16:05:51 -080065 private static final boolean DBG = true;
Gregory Clark67259c22018-03-09 15:30:21 -080066 // The accuracy for the stored timestamp
67 private static final long GRANULARITY_ONE_DAY_MS = 24 * 60 * 60 * 1000L;
68 // The time-to-live for the cached location
69 private static final long TTL_THIRTY_DAYS_MS = 30 * GRANULARITY_ONE_DAY_MS;
Gregory Clarka63ba022018-06-07 16:42:12 -070070 // The maximum number of times to try injecting a location
71 private static final int MAX_LOCATION_INJECTION_ATTEMPTS = 10;
Gregory Clarkd8136062017-12-11 14:27:53 -080072
Gregory Clark3f9b56d2018-01-31 13:02:41 -080073 // Used internally for mHandlerThread synchronization
Gregory Clarkd8136062017-12-11 14:27:53 -080074 private final Object mLock = new Object();
75
Gregory Clark55620c12019-06-03 17:42:58 -070076 // Used internally for mILocationManagerProxy synchronization
77 private final Object mLocationManagerProxyLock = new Object();
78
Gregory Clarkd8136062017-12-11 14:27:53 -080079 private final Context mContext;
Gregory Clarka63ba022018-06-07 16:42:12 -070080 private final CarUserManagerHelper mCarUserManagerHelper;
Gregory Clark3f9b56d2018-01-31 13:02:41 -080081 private int mTaskCount = 0;
82 private HandlerThread mHandlerThread;
83 private Handler mHandler;
Serik Beketayev68920542018-09-06 13:29:58 -070084 private CarPowerManager mCarPowerManager;
Gregory Clark18a0ef92019-05-23 20:00:49 -070085 private CarDrivingStateService mCarDrivingStateService;
Gregory Clark55620c12019-06-03 17:42:58 -070086 private PerUserCarServiceHelper mPerUserCarServiceHelper;
87
88 // Allows us to interact with the {@link LocationManager} as the foreground user.
89 private ILocationManagerProxy mILocationManagerProxy;
90
91 // Maintains mILocationManagerProxy for the current foreground user.
92 private final PerUserCarServiceHelper.ServiceCallback mUserServiceCallback =
93 new PerUserCarServiceHelper.ServiceCallback() {
94 @Override
95 public void onServiceConnected(ICarUserService carUserService) {
96 logd("Connected to PerUserCarService");
97 if (carUserService == null) {
98 logd("ICarUserService is null. Cannot get location manager proxy");
99 return;
100 }
101 synchronized (mLocationManagerProxyLock) {
102 try {
103 mILocationManagerProxy = carUserService.getLocationManagerProxy();
104 } catch (RemoteException e) {
105 Log.e(TAG, "RemoteException from ICarUserService", e);
106 return;
107 }
108 }
109 int currentUser = ActivityManager.getCurrentUser();
110 logd("Current user: " + currentUser);
111 if (mCarUserManagerHelper.isHeadlessSystemUser()
112 && currentUser > UserHandle.USER_SYSTEM) {
113 asyncOperation(() -> loadLocation());
114 }
115 }
116
117 @Override
118 public void onPreUnbind() {
119 logd("Before Unbinding from PerCarUserService");
120 synchronized (mLocationManagerProxyLock) {
121 mILocationManagerProxy = null;
122 }
123 }
124
125 @Override
126 public void onServiceDisconnected() {
127 logd("Disconnected from PerUserCarService");
128 synchronized (mLocationManagerProxyLock) {
129 mILocationManagerProxy = null;
130 }
131 }
132 };
133
134 private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
135 new ICarDrivingStateChangeListener.Stub() {
136 @Override
137 public void onDrivingStateChanged(CarDrivingStateEvent event) {
138 logd("onDrivingStateChanged " + event);
139 if (event != null
140 && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
141 deleteCacheFile();
142 if (mCarDrivingStateService != null) {
143 mCarDrivingStateService.unregisterDrivingStateChangeListener(
144 mICarDrivingStateChangeEventListener);
145 }
146 }
147 }
148 };
Gregory Clarkd8136062017-12-11 14:27:53 -0800149
Gregory Clark18a0ef92019-05-23 20:00:49 -0700150 public CarLocationService(Context context, CarUserManagerHelper carUserManagerHelper) {
Gregory Clarkd8136062017-12-11 14:27:53 -0800151 logd("constructed");
152 mContext = context;
Gregory Clarka63ba022018-06-07 16:42:12 -0700153 mCarUserManagerHelper = carUserManagerHelper;
Gregory Clarkd8136062017-12-11 14:27:53 -0800154 }
155
156 @Override
157 public void init() {
158 logd("init");
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800159 IntentFilter filter = new IntentFilter();
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800160 filter.addAction(LocationManager.MODE_CHANGED_ACTION);
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800161 mContext.registerReceiver(this, filter);
Gregory Clark18a0ef92019-05-23 20:00:49 -0700162 mCarDrivingStateService = CarLocalServices.getService(CarDrivingStateService.class);
163 if (mCarDrivingStateService != null) {
164 CarDrivingStateEvent event = mCarDrivingStateService.getCurrentDrivingState();
165 if (event != null && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
166 deleteCacheFile();
167 } else {
168 mCarDrivingStateService.registerDrivingStateChangeListener(
169 mICarDrivingStateChangeEventListener);
170 }
171 }
Keun young Parka5fa4782019-04-16 18:56:27 -0700172 mCarPowerManager = CarLocalServices.createCarPowerManager(mContext);
173 if (mCarPowerManager != null) { // null case happens for testing.
Jim Kayee5133162019-04-22 12:50:27 -0700174 mCarPowerManager.setListenerWithCompletion(CarLocationService.this);
Keun young Parka5fa4782019-04-16 18:56:27 -0700175 }
Gregory Clark55620c12019-06-03 17:42:58 -0700176 mPerUserCarServiceHelper = CarLocalServices.getService(PerUserCarServiceHelper.class);
177 if (mPerUserCarServiceHelper != null) {
178 mPerUserCarServiceHelper.registerServiceCallback(mUserServiceCallback);
179 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800180 }
181
182 @Override
183 public void release() {
184 logd("release");
Keun young Parka5fa4782019-04-16 18:56:27 -0700185 if (mCarPowerManager != null) {
186 mCarPowerManager.clearListener();
187 }
Gregory Clark18a0ef92019-05-23 20:00:49 -0700188 if (mCarDrivingStateService != null) {
189 mCarDrivingStateService.unregisterDrivingStateChangeListener(
190 mICarDrivingStateChangeEventListener);
191 }
Gregory Clark55620c12019-06-03 17:42:58 -0700192 if (mPerUserCarServiceHelper != null) {
193 mPerUserCarServiceHelper.unregisterServiceCallback(mUserServiceCallback);
194 }
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800195 mContext.unregisterReceiver(this);
Gregory Clarkd8136062017-12-11 14:27:53 -0800196 }
197
198 @Override
199 public void dump(PrintWriter writer) {
200 writer.println(TAG);
201 writer.println("Context: " + mContext);
Gregory Clarka63ba022018-06-07 16:42:12 -0700202 writer.println("MAX_LOCATION_INJECTION_ATTEMPTS: " + MAX_LOCATION_INJECTION_ATTEMPTS);
Gregory Clarkd8136062017-12-11 14:27:53 -0800203 }
204
205 @Override
Serik Beketayev68920542018-09-06 13:29:58 -0700206 public void onStateChanged(int state, CompletableFuture<Void> future) {
Steve Paik07db5ed2018-09-24 16:48:52 -0700207 logd("onStateChanged: " + state);
Serik Beketayev68920542018-09-06 13:29:58 -0700208 switch (state) {
Steve Paik07db5ed2018-09-24 16:48:52 -0700209 case CarPowerStateListener.SHUTDOWN_PREPARE:
Gregory Clark5b9a9cd2018-09-10 13:21:22 -0700210 asyncOperation(() -> {
211 storeLocation();
212 // Notify the CarPowerManager that it may proceed to shutdown or suspend.
Gregory Clark71ed6722018-09-24 13:10:16 -0700213 if (future != null) {
214 future.complete(null);
215 }
Gregory Clark5b9a9cd2018-09-10 13:21:22 -0700216 });
Serik Beketayev68920542018-09-06 13:29:58 -0700217 break;
Gregory Clark63a4ad22019-05-23 18:36:33 -0700218 case CarPowerStateListener.SUSPEND_EXIT:
Gregory Clarkdfb084f2019-07-15 16:41:57 -0700219 if (mCarDrivingStateService != null) {
220 CarDrivingStateEvent event = mCarDrivingStateService.getCurrentDrivingState();
221 if (event != null
222 && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
223 deleteCacheFile();
224 } else {
225 logd("Registering to receive driving state.");
226 mCarDrivingStateService.registerDrivingStateChangeListener(
227 mICarDrivingStateChangeEventListener);
228 }
229 }
Gregory Clark63a4ad22019-05-23 18:36:33 -0700230 if (future != null) {
231 future.complete(null);
232 }
Steve Paik07db5ed2018-09-24 16:48:52 -0700233 default:
Gregory Clark5b9a9cd2018-09-10 13:21:22 -0700234 // This service does not need to do any work for these events but should still
235 // notify the CarPowerManager that it may proceed.
Gregory Clark71ed6722018-09-24 13:10:16 -0700236 if (future != null) {
237 future.complete(null);
238 }
Serik Beketayev68920542018-09-06 13:29:58 -0700239 break;
240 }
Gregory Clark26fa6012018-03-14 18:38:56 -0700241 }
242
Gregory Clark26fa6012018-03-14 18:38:56 -0700243 @Override
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800244 public void onReceive(Context context, Intent intent) {
Gregory Clark26fa6012018-03-14 18:38:56 -0700245 logd("onReceive " + intent);
Gregory Clark55620c12019-06-03 17:42:58 -0700246 // If the system user is headless but the current user is still the system user, then we
247 // should not delete the location cache file due to missing location permissions.
248 if (isCurrentUserHeadlessSystemUser()) {
249 logd("Current user is headless system user.");
250 return;
251 }
252 synchronized (mLocationManagerProxyLock) {
253 if (mILocationManagerProxy == null) {
254 logd("Null location manager.");
255 return;
Gregory Clarka63ba022018-06-07 16:42:12 -0700256 }
Gregory Clark55620c12019-06-03 17:42:58 -0700257 String action = intent.getAction();
258 try {
259 if (action == LocationManager.MODE_CHANGED_ACTION) {
260 boolean locationEnabled = mILocationManagerProxy.isLocationEnabled();
261 logd("isLocationEnabled(): " + locationEnabled);
262 if (!locationEnabled) {
263 deleteCacheFile();
264 }
265 } else {
266 logd("Unexpected intent.");
267 }
268 } catch (RemoteException e) {
269 Log.e(TAG, "RemoteException from ILocationManagerProxy", e);
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800270 }
271 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800272 }
273
Gregory Clark55620c12019-06-03 17:42:58 -0700274 /** Tells whether the current foreground user is the headless system user. */
275 private boolean isCurrentUserHeadlessSystemUser() {
276 int currentUserId = ActivityManager.getCurrentUser();
277 return mCarUserManagerHelper.isHeadlessSystemUser() && currentUserId == 0;
Gregory Clarka63ba022018-06-07 16:42:12 -0700278 }
279
280 /**
Gregory Clark55620c12019-06-03 17:42:58 -0700281 * Gets the last known location from the location manager proxy and store it in a file.
Gregory Clarka63ba022018-06-07 16:42:12 -0700282 */
Gregory Clarkd8136062017-12-11 14:27:53 -0800283 private void storeLocation() {
Gregory Clark55620c12019-06-03 17:42:58 -0700284 Location location = null;
285 synchronized (mLocationManagerProxyLock) {
286 if (mILocationManagerProxy == null) {
287 logd("Null location manager proxy.");
288 return;
289 }
290 try {
291 location = mILocationManagerProxy.getLastKnownLocation(
292 LocationManager.GPS_PROVIDER);
293 } catch (RemoteException e) {
294 Log.e(TAG, "RemoteException from ILocationManagerProxy", e);
295 }
296 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800297 if (location == null) {
298 logd("Not storing null location");
299 } else {
300 logd("Storing location: " + location);
Gregory Clarka440e812019-02-14 16:05:51 -0800301 AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
Gregory Clarkd8136062017-12-11 14:27:53 -0800302 FileOutputStream fos = null;
303 try {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800304 fos = atomicFile.startWrite();
Gregory Clarka63ba022018-06-07 16:42:12 -0700305 try (JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(fos, "UTF-8"))) {
306 jsonWriter.beginObject();
307 jsonWriter.name("provider").value(location.getProvider());
308 jsonWriter.name("latitude").value(location.getLatitude());
309 jsonWriter.name("longitude").value(location.getLongitude());
310 if (location.hasAltitude()) {
311 jsonWriter.name("altitude").value(location.getAltitude());
312 }
313 if (location.hasSpeed()) {
314 jsonWriter.name("speed").value(location.getSpeed());
315 }
316 if (location.hasBearing()) {
317 jsonWriter.name("bearing").value(location.getBearing());
318 }
319 if (location.hasAccuracy()) {
320 jsonWriter.name("accuracy").value(location.getAccuracy());
321 }
322 if (location.hasVerticalAccuracy()) {
323 jsonWriter.name("verticalAccuracy").value(
324 location.getVerticalAccuracyMeters());
325 }
326 if (location.hasSpeedAccuracy()) {
327 jsonWriter.name("speedAccuracy").value(
328 location.getSpeedAccuracyMetersPerSecond());
329 }
330 if (location.hasBearingAccuracy()) {
331 jsonWriter.name("bearingAccuracy").value(
332 location.getBearingAccuracyDegrees());
333 }
334 if (location.isFromMockProvider()) {
335 jsonWriter.name("isFromMockProvider").value(true);
336 }
337 long currentTime = location.getTime();
338 // Round the time down to only be accurate within one day.
339 jsonWriter.name("captureTime").value(
340 currentTime - currentTime % GRANULARITY_ONE_DAY_MS);
341 jsonWriter.endObject();
Gregory Clarkd8136062017-12-11 14:27:53 -0800342 }
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800343 atomicFile.finishWrite(fos);
Gregory Clarkd8136062017-12-11 14:27:53 -0800344 } catch (IOException e) {
345 Log.e(TAG, "Unable to write to disk", e);
346 atomicFile.failWrite(fos);
347 }
348 }
349 }
350
Gregory Clarka63ba022018-06-07 16:42:12 -0700351 /**
Gregory Clark55620c12019-06-03 17:42:58 -0700352 * Reads a previously stored location and attempts to inject it into the location manager proxy.
Gregory Clarka63ba022018-06-07 16:42:12 -0700353 */
Gregory Clarkd8136062017-12-11 14:27:53 -0800354 private void loadLocation() {
Gregory Clark67259c22018-03-09 15:30:21 -0800355 Location location = readLocationFromCacheFile();
Gregory Clarka440e812019-02-14 16:05:51 -0800356 logd("Read location from timestamp " + location.getTime());
Gregory Clark67259c22018-03-09 15:30:21 -0800357 long currentTime = System.currentTimeMillis();
358 if (location.getTime() + TTL_THIRTY_DAYS_MS < currentTime) {
359 logd("Location expired.");
Gregory Clark63a4ad22019-05-23 18:36:33 -0700360 deleteCacheFile();
Gregory Clark67259c22018-03-09 15:30:21 -0800361 } else {
362 location.setTime(currentTime);
363 long elapsedTime = SystemClock.elapsedRealtimeNanos();
364 location.setElapsedRealtimeNanos(elapsedTime);
365 if (location.isComplete()) {
Gregory Clarka63ba022018-06-07 16:42:12 -0700366 injectLocation(location, 1);
Gregory Clark67259c22018-03-09 15:30:21 -0800367 }
368 }
369 }
370
371 private Location readLocationFromCacheFile() {
Gregory Clarkd8136062017-12-11 14:27:53 -0800372 Location location = new Location((String) null);
Gregory Clarka440e812019-02-14 16:05:51 -0800373 AtomicFile atomicFile = new AtomicFile(getLocationCacheFile());
Gregory Clarka63ba022018-06-07 16:42:12 -0700374 try (FileInputStream fis = atomicFile.openRead()) {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800375 JsonReader reader = new JsonReader(new InputStreamReader(fis, "UTF-8"));
376 reader.beginObject();
377 while (reader.hasNext()) {
378 String name = reader.nextName();
379 if (name.equals("provider")) {
380 location.setProvider(reader.nextString());
381 } else if (name.equals("latitude")) {
382 location.setLatitude(reader.nextDouble());
383 } else if (name.equals("longitude")) {
384 location.setLongitude(reader.nextDouble());
385 } else if (name.equals("altitude")) {
386 location.setAltitude(reader.nextDouble());
387 } else if (name.equals("speed")) {
388 location.setSpeed((float) reader.nextDouble());
389 } else if (name.equals("bearing")) {
390 location.setBearing((float) reader.nextDouble());
391 } else if (name.equals("accuracy")) {
392 location.setAccuracy((float) reader.nextDouble());
393 } else if (name.equals("verticalAccuracy")) {
394 location.setVerticalAccuracyMeters((float) reader.nextDouble());
395 } else if (name.equals("speedAccuracy")) {
396 location.setSpeedAccuracyMetersPerSecond((float) reader.nextDouble());
397 } else if (name.equals("bearingAccuracy")) {
398 location.setBearingAccuracyDegrees((float) reader.nextDouble());
399 } else if (name.equals("isFromMockProvider")) {
400 location.setIsFromMockProvider(reader.nextBoolean());
Gregory Clarke70eb5b2018-02-07 18:29:24 -0800401 } else if (name.equals("captureTime")) {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800402 location.setTime(reader.nextLong());
403 } else {
404 reader.skipValue();
Gregory Clarkd8136062017-12-11 14:27:53 -0800405 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800406 }
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800407 reader.endObject();
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800408 } catch (FileNotFoundException e) {
409 Log.d(TAG, "Location cache file not found.");
Gregory Clarkd8136062017-12-11 14:27:53 -0800410 } catch (IOException e) {
411 Log.e(TAG, "Unable to read from disk", e);
412 } catch (NumberFormatException | IllegalStateException e) {
413 Log.e(TAG, "Unexpected format", e);
414 }
Gregory Clark67259c22018-03-09 15:30:21 -0800415 return location;
Gregory Clarkd8136062017-12-11 14:27:53 -0800416 }
417
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800418 private void deleteCacheFile() {
Gregory Clarka440e812019-02-14 16:05:51 -0800419 boolean deleted = getLocationCacheFile().delete();
420 logd("Deleted cache file: " + deleted);
Gregory Clark29b7f0f2018-02-16 17:53:40 -0800421 }
422
Gregory Clarka63ba022018-06-07 16:42:12 -0700423 /**
424 * Attempts to inject the location multiple times in case the LocationManager was not fully
425 * initialized or has not updated its handle to the current user yet.
426 */
427 private void injectLocation(Location location, int attemptCount) {
Gregory Clark55620c12019-06-03 17:42:58 -0700428 boolean success = false;
429 synchronized (mLocationManagerProxyLock) {
430 if (mILocationManagerProxy == null) {
431 logd("Null location manager proxy.");
432 } else {
433 try {
434 success = mILocationManagerProxy.injectLocation(location);
435 } catch (RemoteException e) {
436 Log.e(TAG, "RemoteException from ILocationManagerProxy", e);
437 }
438 }
439 }
Gregory Clarka63ba022018-06-07 16:42:12 -0700440 logd("Injected location " + location + " with result " + success + " on attempt "
441 + attemptCount);
442 if (success) {
443 return;
444 } else if (attemptCount <= MAX_LOCATION_INJECTION_ATTEMPTS) {
445 asyncOperation(() -> {
446 injectLocation(location, attemptCount + 1);
447 }, 200 * attemptCount);
448 } else {
449 logd("No location injected.");
450 }
451 }
452
Gregory Clarka440e812019-02-14 16:05:51 -0800453 private File getLocationCacheFile() {
454 SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
455 File file = new File(systemInterface.getSystemCarDir(), FILENAME);
456 logd("File: " + file);
457 return file;
458 }
459
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800460 @VisibleForTesting
461 void asyncOperation(Runnable operation) {
Gregory Clarka63ba022018-06-07 16:42:12 -0700462 asyncOperation(operation, 0);
463 }
464
465 private void asyncOperation(Runnable operation, long delayMillis) {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800466 synchronized (mLock) {
467 // Create a new HandlerThread if this is the first task to queue.
468 if (++mTaskCount == 1) {
469 mHandlerThread = new HandlerThread("CarLocationServiceThread");
470 mHandlerThread.start();
471 mHandler = new Handler(mHandlerThread.getLooper());
472 }
473 }
Gregory Clarka63ba022018-06-07 16:42:12 -0700474 mHandler.postDelayed(() -> {
Gregory Clark3f9b56d2018-01-31 13:02:41 -0800475 try {
476 operation.run();
477 } finally {
478 synchronized (mLock) {
479 // Quit the thread when the task queue is empty.
480 if (--mTaskCount == 0) {
481 mHandler.getLooper().quit();
482 mHandler = null;
483 mHandlerThread = null;
484 }
485 }
486 }
Gregory Clarka63ba022018-06-07 16:42:12 -0700487 }, delayMillis);
Gregory Clarkd8136062017-12-11 14:27:53 -0800488 }
489
490 private static void logd(String msg) {
491 if (DBG) {
492 Log.d(TAG, msg);
493 }
494 }
Gregory Clarkd8136062017-12-11 14:27:53 -0800495}