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