• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.server.location.provider;
18 
19 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
20 
21 import android.annotation.Nullable;
22 import android.location.Location;
23 import android.location.LocationResult;
24 import android.location.provider.ProviderRequest;
25 import android.os.Bundle;
26 
27 import com.android.internal.annotations.GuardedBy;
28 import com.android.internal.util.Preconditions;
29 
30 import java.io.FileDescriptor;
31 import java.io.PrintWriter;
32 import java.util.Collections;
33 
34 /**
35  * Represents a location provider that may switch between a mock implementation and a real
36  * implementation. Requires owners to provide a lock object that will be used internally and held
37  * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead
38  * to deadlock.
39  *
40  * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can
41  * be held BOTH when calling into this class AND when receiving a callback from this class is the
42  * lock given to this class via the constructor. Holding any other lock is ok as long as there is no
43  * possibility that it can be obtained within both codepaths.
44  *
45  * Holding the given lock guarantees atomicity of any operations on this class for the duration.
46  *
47  * @hide
48  */
49 public class MockableLocationProvider extends AbstractLocationProvider {
50 
51     final Object mOwnerLock;
52 
53     @GuardedBy("mOwnerLock")
54     private @Nullable AbstractLocationProvider mProvider;
55     @GuardedBy("mOwnerLock")
56     private @Nullable AbstractLocationProvider mRealProvider;
57     @GuardedBy("mOwnerLock")
58     private @Nullable MockLocationProvider mMockProvider;
59 
60     @GuardedBy("mOwnerLock")
61     private boolean mStarted;
62     @GuardedBy("mOwnerLock")
63     private ProviderRequest mRequest;
64 
65     /**
66      * The given lock object will be held any time the listener is invoked, and may also be acquired
67      * and released during the course of invoking any public methods. Holding the given lock ensures
68      * that provider state cannot change except as result of an explicit call by the owner of the
69      * lock into this class. The client is reponsible for ensuring this cannot cause deadlock.
70      *
71      * The client should expect that it may being to receive callbacks as soon as this constructor
72      * is invoked.
73      */
MockableLocationProvider(Object ownerLock)74     public MockableLocationProvider(Object ownerLock) {
75         // using a direct executor is acceptable because all inbound calls are delegated to the
76         // actual provider implementations which will use their own executors
77         super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
78         mOwnerLock = ownerLock;
79         mRequest = ProviderRequest.EMPTY_REQUEST;
80     }
81 
82     /**
83      * Returns the current provider implementation. May be null if there is no current
84      * implementation.
85      */
86     @Nullable
getProvider()87     public AbstractLocationProvider getProvider() {
88         synchronized (mOwnerLock) {
89             return mProvider;
90         }
91     }
92 
93     /**
94      * Sets the real provider implementation, replacing any previous real provider implementation.
95      * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this
96      * results in a state change.
97      */
setRealProvider(@ullable AbstractLocationProvider provider)98     public void setRealProvider(@Nullable AbstractLocationProvider provider) {
99         synchronized (mOwnerLock) {
100             if (mRealProvider == provider) {
101                 return;
102             }
103 
104             mRealProvider = provider;
105             if (!isMock()) {
106                 setProviderLocked(mRealProvider);
107             }
108         }
109     }
110 
111     /**
112      * Sets the mock provider implementation, replacing any previous mock provider implementation.
113      * Mock implementations are always used instead of real implementations if set. May cause an
114      * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a
115      * state change.
116      */
setMockProvider(@ullable MockLocationProvider provider)117     public void setMockProvider(@Nullable MockLocationProvider provider) {
118         synchronized (mOwnerLock) {
119             if (mMockProvider == provider) {
120                 return;
121             }
122 
123             mMockProvider = provider;
124             if (mMockProvider != null) {
125                 setProviderLocked(mMockProvider);
126             } else {
127                 setProviderLocked(mRealProvider);
128             }
129         }
130     }
131 
132     @GuardedBy("mOwnerLock")
setProviderLocked(@ullable AbstractLocationProvider provider)133     private void setProviderLocked(@Nullable AbstractLocationProvider provider) {
134         if (mProvider == provider) {
135             return;
136         }
137 
138         AbstractLocationProvider oldProvider = mProvider;
139         mProvider = provider;
140 
141         if (oldProvider != null) {
142             // do this after switching the provider - so even if the old provider is using a direct
143             // executor, if it re-enters this class within setRequest(), it will be ignored
144             oldProvider.getController().setListener(null);
145             if (oldProvider.getController().isStarted()) {
146                 oldProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST);
147                 oldProvider.getController().stop();
148             }
149         }
150 
151         State newState;
152         if (mProvider != null) {
153             newState = mProvider.getController().setListener(new ListenerWrapper(mProvider));
154             if (mStarted) {
155                 if (!mProvider.getController().isStarted()) {
156                     mProvider.getController().start();
157                 }
158                 mProvider.getController().setRequest(mRequest);
159             } else {
160                 if (mProvider.getController().isStarted()) {
161                     mProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST);
162                     mProvider.getController().stop();
163                 }
164             }
165         } else {
166             newState = State.EMPTY_STATE;
167         }
168 
169         setState(prevState -> newState);
170     }
171 
172     /**
173      * Returns true if the current active provider implementation is the mock implementation, and
174      * false otherwise.
175      */
isMock()176     public boolean isMock() {
177         synchronized (mOwnerLock) {
178             return mMockProvider != null && mProvider == mMockProvider;
179         }
180     }
181 
182     /**
183      * Sets the mock provider implementation's allowed state. Will throw an exception if the mock
184      * provider is not currently the active implementation.
185      */
setMockProviderAllowed(boolean allowed)186     public void setMockProviderAllowed(boolean allowed) {
187         synchronized (mOwnerLock) {
188             Preconditions.checkState(isMock());
189             mMockProvider.setProviderAllowed(allowed);
190         }
191     }
192     /**
193      * Sets the mock provider implementation's location. Will throw an exception if the mock
194      * provider is not currently the active implementation.
195      */
setMockProviderLocation(Location location)196     public void setMockProviderLocation(Location location) {
197         synchronized (mOwnerLock) {
198             Preconditions.checkState(isMock());
199             mMockProvider.setProviderLocation(location);
200         }
201     }
202 
203     /**
204      * Returns the current location request.
205      */
getCurrentRequest()206     public ProviderRequest getCurrentRequest() {
207         synchronized (mOwnerLock) {
208             return mRequest;
209         }
210     }
211 
212     @Override
onStart()213     protected void onStart() {
214         synchronized (mOwnerLock) {
215             Preconditions.checkState(!mStarted);
216 
217             mStarted = true;
218 
219             if (mProvider != null) {
220                 mProvider.getController().start();
221                 if (!mRequest.equals(ProviderRequest.EMPTY_REQUEST)) {
222                     mProvider.getController().setRequest(mRequest);
223                 }
224             }
225         }
226     }
227 
228     @Override
onStop()229     protected void onStop() {
230         synchronized (mOwnerLock) {
231             Preconditions.checkState(mStarted);
232 
233             mStarted = false;
234 
235             if (mProvider != null) {
236                 if (!mRequest.equals(ProviderRequest.EMPTY_REQUEST)) {
237                     mProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST);
238                 }
239                 mProvider.getController().stop();
240             }
241 
242             mRequest = ProviderRequest.EMPTY_REQUEST;
243         }
244     }
245 
246     @Override
onSetRequest(ProviderRequest request)247     protected void onSetRequest(ProviderRequest request) {
248         synchronized (mOwnerLock) {
249             if (request == mRequest) {
250                 return;
251             }
252 
253             mRequest = request;
254 
255             if (mProvider != null) {
256                 mProvider.getController().setRequest(request);
257             }
258         }
259     }
260 
261     @Override
onFlush(Runnable callback)262     protected void onFlush(Runnable callback) {
263         synchronized (mOwnerLock) {
264             if (mProvider != null) {
265                 mProvider.getController().flush(callback);
266             } else {
267                 callback.run();
268             }
269         }
270     }
271 
272     @Override
onExtraCommand(int uid, int pid, String command, Bundle extras)273     protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
274         synchronized (mOwnerLock) {
275             if (mProvider != null) {
276                 mProvider.getController().sendExtraCommand(uid, pid, command, extras);
277             }
278         }
279     }
280 
281     /**
282      * Dumps the current provider implementation.
283      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)284     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
285         // holding the owner lock outside this could lead to deadlock since we don't run dump on the
286         // executor specified by the provider, we run it directly
287         Preconditions.checkState(!Thread.holdsLock(mOwnerLock));
288 
289         AbstractLocationProvider provider;
290         State providerState;
291         synchronized (mOwnerLock) {
292             provider = mProvider;
293             providerState = getState();
294         }
295 
296         pw.println("allowed=" + providerState.allowed);
297         if (providerState.identity != null) {
298             pw.println("identity=" + providerState.identity);
299         }
300         if (!providerState.extraAttributionTags.isEmpty()) {
301             pw.println("extra attribution tags=" + providerState.extraAttributionTags);
302         }
303         if (providerState.properties != null) {
304             pw.println("properties=" + providerState.properties);
305         }
306 
307         if (provider != null) {
308             // dump outside the lock in case the provider wants to acquire its own locks, and since
309             // the default provider dump behavior does not move things onto the provider thread...
310             provider.dump(fd, pw, args);
311         }
312     }
313 
314     // ensures that callbacks from the incorrect provider are never visible to clients - this
315     // requires holding the owner's lock for the duration of the callback
316     private class ListenerWrapper implements Listener {
317 
318         private final AbstractLocationProvider mListenerProvider;
319 
ListenerWrapper(AbstractLocationProvider listenerProvider)320         ListenerWrapper(AbstractLocationProvider listenerProvider) {
321             mListenerProvider = listenerProvider;
322         }
323 
324         @Override
onStateChanged(State oldState, State newState)325         public final void onStateChanged(State oldState, State newState) {
326             synchronized (mOwnerLock) {
327                 if (mListenerProvider != mProvider) {
328                     return;
329                 }
330 
331                 setState(prevState -> newState);
332             }
333         }
334 
335         @Override
onReportLocation(LocationResult locationResult)336         public final void onReportLocation(LocationResult locationResult) {
337             synchronized (mOwnerLock) {
338                 if (mListenerProvider != mProvider) {
339                     return;
340                 }
341 
342                 reportLocation(locationResult);
343             }
344         }
345     }
346 }
347