diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 05999ae13b8d6..345dc217110b3 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -243,15 +243,24 @@ public class LocationProviderManager extends intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0])); } + // send() SHOULD only run the completion callback if it completes successfully. however, + // b/199464864 (which could not be fixed in the S timeframe) means that it's possible + // for send() to throw an exception AND run the completion callback. if this happens, we + // would over-release the wakelock... we take matters into our own hands to ensure that + // the completion callback can only be run if send() completes successfully. this means + // the completion callback may be run inline - but as we've never specified what thread + // the callback is run on, this is fine. + GatedCallback gatedCallback = new GatedCallback(onCompleteCallback); + mPendingIntent.send( mContext, 0, intent, - onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run() - : null, + (pI, i, rC, rD, rE) -> gatedCallback.run(), null, null, options.toBundle()); + gatedCallback.allow(); } @Override @@ -2734,4 +2743,49 @@ public class LocationProviderManager extends } } } + + private static class GatedCallback implements Runnable { + + private @Nullable Runnable mCallback; + + @GuardedBy("this") + private boolean mGate; + @GuardedBy("this") + private boolean mRun; + + GatedCallback(Runnable callback) { + mCallback = callback; + } + + public void allow() { + Runnable callback = null; + synchronized (this) { + mGate = true; + if (mRun && mCallback != null) { + callback = mCallback; + mCallback = null; + } + } + + if (callback != null) { + callback.run(); + } + } + + @Override + public void run() { + Runnable callback = null; + synchronized (this) { + mRun = true; + if (mGate && mCallback != null) { + callback = mCallback; + mCallback = null; + } + } + + if (callback != null) { + callback.run(); + } + } + } }