diff options
7 files changed, 303 insertions, 97 deletions
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java index bdfa700215f8..9cdd54c16a42 100644 --- a/core/java/android/text/style/StyleSpan.java +++ b/core/java/android/text/style/StyleSpan.java @@ -17,8 +17,10 @@ package android.text.style; import android.annotation.NonNull; +import android.content.res.Configuration; import android.graphics.Paint; import android.graphics.Typeface; +import android.graphics.fonts.FontStyle; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; @@ -45,6 +47,7 @@ import android.text.TextUtils; public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mStyle; + private final int mFontWeightAdjustment; /** * Creates a {@link StyleSpan} from a style. @@ -54,7 +57,24 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { * in {@link Typeface}. */ public StyleSpan(int style) { + this(style, Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED); + } + + /** + * Creates a {@link StyleSpan} from a style and font weight adjustment. + * + * @param style An integer constant describing the style for this span. Examples + * include bold, italic, and normal. Values are constants defined + * in {@link Typeface}. + * @param fontWeightAdjustment An integer describing the adjustment to be made to the font + * weight. + * @see Configuration#fontWeightAdjustment This is the adjustment in text font weight + * that is used to reflect the current user's preference for increasing font weight. + * @hide + */ + public StyleSpan(@Typeface.Style int style, int fontWeightAdjustment) { mStyle = style; + mFontWeightAdjustment = fontWeightAdjustment; } /** @@ -64,6 +84,7 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { */ public StyleSpan(@NonNull Parcel src) { mStyle = src.readInt(); + mFontWeightAdjustment = src.readInt(); } @Override @@ -91,6 +112,7 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { @Override public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mStyle); + dest.writeInt(mFontWeightAdjustment); } /** @@ -100,17 +122,25 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { return mStyle; } + /** + * Returns the font weight adjustment specified by this span. + * @hide + */ + public int getFontWeightAdjustment() { + return mFontWeightAdjustment; + } + @Override public void updateDrawState(TextPaint ds) { - apply(ds, mStyle); + apply(ds, mStyle, mFontWeightAdjustment); } @Override public void updateMeasureState(TextPaint paint) { - apply(paint, mStyle); + apply(paint, mStyle, mFontWeightAdjustment); } - private static void apply(Paint paint, int style) { + private static void apply(Paint paint, int style, int fontWeightAdjustment) { int oldStyle; Typeface old = paint.getTypeface(); @@ -129,6 +159,18 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { tf = Typeface.create(old, want); } + // Base typeface may already be bolded by auto bold. Bold further. + if ((style & Typeface.BOLD) != 0) { + if (fontWeightAdjustment != 0 + && fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { + int newWeight = Math.min( + Math.max(tf.getWeight() + fontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), + FontStyle.FONT_WEIGHT_MAX); + boolean italic = (want & Typeface.ITALIC) != 0; + tf = Typeface.create(tf, newWeight, italic); + } + } + int fake = want & ~tf.getStyle(); if ((fake & Typeface.BOLD) != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index d8f6a01398d1..df20b83a36ca 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -49,7 +49,6 @@ import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Bundle; import android.os.Handler; -import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.util.SparseBooleanArray; @@ -65,6 +64,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.util.concurrency.Execution; import java.util.ArrayList; import java.util.Arrays; @@ -92,15 +92,20 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, private static final boolean DEBUG = true; private static final int SENSOR_PRIVACY_DELAY = 500; - private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Handler mHandler; + private final Execution mExecution; private final CommandQueue mCommandQueue; private final ActivityTaskManager mActivityTaskManager; - @Nullable private final FingerprintManager mFingerprintManager; - @Nullable private final FaceManager mFaceManager; + @Nullable + private final FingerprintManager mFingerprintManager; + @Nullable + private final FaceManager mFaceManager; private final Provider<UdfpsController> mUdfpsControllerFactory; private final Provider<SidefpsController> mSidefpsControllerFactory; - @Nullable private final PointF mFaceAuthSensorLocation; - @Nullable private PointF mFingerprintLocation; + @Nullable + private final PointF mFaceAuthSensorLocation; + @Nullable + private PointF mFingerprintLocation; private final Set<Callback> mCallbacks = new HashSet<>(); // TODO: These should just be saved from onSaveState @@ -133,62 +138,27 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } } - private final FingerprintStateListener mFingerprintStateListener = - new FingerprintStateListener() { - @Override - public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { - Log.d(TAG, "onEnrollmentsChanged, userId: " + userId - + ", sensorId: " + sensorId - + ", hasEnrollments: " + hasEnrollments); - for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) { - if (prop.sensorId == sensorId) { - mUdfpsEnrolledForUser.put(userId, hasEnrollments); - } - } - - for (Callback cb : mCallbacks) { - cb.onEnrollmentsChanged(); - } - } - }; - - @NonNull private final IFingerprintAuthenticatorsRegisteredCallback mFingerprintAuthenticatorsRegisteredCallback = new IFingerprintAuthenticatorsRegisteredCallback.Stub() { - @Override public void onAllAuthenticatorsRegistered( + @Override + public void onAllAuthenticatorsRegistered( List<FingerprintSensorPropertiesInternal> sensors) { - if (DEBUG) { - Log.d(TAG, "onFingerprintProvidersAvailable | sensors: " + Arrays.toString( - sensors.toArray())); - } - mFpProps = sensors; - List<FingerprintSensorPropertiesInternal> udfpsProps = new ArrayList<>(); - List<FingerprintSensorPropertiesInternal> sidefpsProps = new ArrayList<>(); - for (FingerprintSensorPropertiesInternal props : mFpProps) { - if (props.isAnyUdfpsType()) { - udfpsProps.add(props); - } - if (props.isAnySidefpsType()) { - sidefpsProps.add(props); - } - } - mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null; - if (mUdfpsProps != null) { - mUdfpsController = mUdfpsControllerFactory.get(); - } - mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; - if (mSidefpsProps != null) { - mSidefpsController = mSidefpsControllerFactory.get(); - } + mHandler.post(() -> handleAllAuthenticatorsRegistered(sensors)); + } + }; - for (Callback cb : mCallbacks) { - cb.onAllAuthenticatorsRegistered(); - } + private final FingerprintStateListener mFingerprintStateListener = + new FingerprintStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mHandler.post( + () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments)); } }; - @VisibleForTesting final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @VisibleForTesting + final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mCurrentDialog != null @@ -212,6 +182,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, }; private void handleTaskStackChanged() { + mExecution.assertIsMainThread(); if (mCurrentDialog != null) { try { final String clientPackage = mCurrentDialog.getOpPackageName(); @@ -241,6 +212,56 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } } + private void handleAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> sensors) { + mExecution.assertIsMainThread(); + if (DEBUG) { + Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString( + sensors.toArray())); + } + mFpProps = sensors; + List<FingerprintSensorPropertiesInternal> udfpsProps = new ArrayList<>(); + List<FingerprintSensorPropertiesInternal> sidefpsProps = new ArrayList<>(); + for (FingerprintSensorPropertiesInternal props : mFpProps) { + if (props.isAnyUdfpsType()) { + udfpsProps.add(props); + } + if (props.isAnySidefpsType()) { + sidefpsProps.add(props); + } + } + mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null; + if (mUdfpsProps != null) { + mUdfpsController = mUdfpsControllerFactory.get(); + } + mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; + if (mSidefpsProps != null) { + mSidefpsController = mSidefpsControllerFactory.get(); + } + for (Callback cb : mCallbacks) { + cb.onAllAuthenticatorsRegistered(); + } + mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener); + } + + private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mExecution.assertIsMainThread(); + Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId + + ", hasEnrollments: " + hasEnrollments); + if (mUdfpsProps == null) { + Log.d(TAG, "handleEnrollmentsChanged, mUdfpsProps is null"); + } else { + for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) { + if (prop.sensorId == sensorId) { + mUdfpsEnrolledForUser.put(userId, hasEnrollments); + } + } + } + for (Callback cb : mCallbacks) { + cb.onEnrollmentsChanged(); + } + } + /** * Adds a callback. See {@link Callback}. */ @@ -449,6 +470,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Inject public AuthController(Context context, + Execution execution, CommandQueue commandQueue, ActivityTaskManager activityTaskManager, @NonNull WindowManager windowManager, @@ -459,6 +481,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @NonNull DisplayManager displayManager, @Main Handler handler) { super(context); + mExecution = execution; + mHandler = handler; mCommandQueue = commandQueue; mActivityTaskManager = activityTaskManager; mFingerprintManager = fingerprintManager; @@ -470,7 +494,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mOrientationListener = new BiometricDisplayListener( context, displayManager, - handler, + mHandler, BiometricDisplayListener.SensorType.Generic.INSTANCE, () -> { onOrientationChanged(); @@ -521,7 +545,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, if (mFingerprintManager != null) { mFingerprintManager.addAuthenticatorsRegisteredCallback( mFingerprintAuthenticatorsRegisteredCallback); - mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener); } mTaskStackListener = new BiometricTaskStackListener(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java index aa511c66eabb..7269f5545163 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java @@ -255,7 +255,7 @@ public class NotificationSnooze extends LinearLayout return new NotificationSnoozeOption(null, minutes, description, resultText, action); } SpannableString string = new SpannableString(resultText); - string.setSpan(new StyleSpan(Typeface.BOLD), + string.setSpan(new StyleSpan(Typeface.BOLD, res.getConfiguration().fontWeightAdjustment), index, index + description.length(), 0 /* flags */); return new NotificationSnoozeOption(null, minutes, description, string, action); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 8dd5d6c01394..08c77146d34c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -53,12 +54,14 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.FingerprintStateListener; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.WindowManager; @@ -67,6 +70,8 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.util.concurrency.Execution; +import com.android.systemui.util.concurrency.FakeExecution; import org.junit.Before; import org.junit.Test; @@ -112,20 +117,27 @@ public class AuthControllerTest extends SysuiTestCase { private SidefpsController mSidefpsController; @Mock private DisplayManager mDisplayManager; - @Mock - private Handler mHandler; @Captor ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor; + @Captor + ArgumentCaptor<FingerprintStateListener> mFingerprintStateCaptor; + private TestableContext mContextSpy; + private Execution mExecution; + private TestableLooper mTestableLooper; + private Handler mHandler; private TestableAuthController mAuthController; @Before public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); - TestableContext context = spy(mContext); + mContextSpy = spy(mContext); + mExecution = new FakeExecution(); + mTestableLooper = TestableLooper.get(this); + mHandler = new Handler(mTestableLooper.getLooper()); - when(context.getPackageManager()).thenReturn(mPackageManager); + when(mContextSpy.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) .thenReturn(true); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) @@ -158,21 +170,78 @@ public class AuthControllerTest extends SysuiTestCase { props.add(prop); when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); - mAuthController = new TestableAuthController(context, mCommandQueue, + mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, () -> mUdfpsController, () -> mSidefpsController); mAuthController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( mAuthenticatorsRegisteredCaptor.capture()); + mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props); + + // Ensures that the operations posted on the handler get executed. + mTestableLooper.processAllMessages(); } // Callback tests @Test + public void testRegistersFingerprintStateListener_afterAllAuthenticatorsAreRegistered() + throws RemoteException { + // This test is sensitive to prior FingerprintManager interactions. + reset(mFingerprintManager); + + // This test requires an uninitialized AuthController. + AuthController authController = new TestableAuthController(mContextSpy, mExecution, + mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, + mFaceManager, () -> mUdfpsController, () -> mSidefpsController); + authController.start(); + + verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( + mAuthenticatorsRegisteredCaptor.capture()); + mTestableLooper.processAllMessages(); + + verify(mFingerprintManager, never()).registerFingerprintStateListener(any()); + + mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>()); + mTestableLooper.processAllMessages(); + + verify(mFingerprintManager).registerFingerprintStateListener(any()); + } + + @Test + public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException { + // This test is sensitive to prior FingerprintManager interactions. + reset(mFingerprintManager); + + // This test requires an uninitialized AuthController. + AuthController authController = new TestableAuthController(mContextSpy, mExecution, + mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, + mFaceManager, () -> mUdfpsController, () -> mSidefpsController); + authController.start(); + + verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( + mAuthenticatorsRegisteredCaptor.capture()); + + // Emulates a device with no authenticators (empty list). + mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>()); + mTestableLooper.processAllMessages(); + + verify(mFingerprintManager).registerFingerprintStateListener( + mFingerprintStateCaptor.capture()); + + // Enrollments changed for an unknown sensor. + mFingerprintStateCaptor.getValue().onEnrollmentsChanged(0 /* userId */, + 0xbeef /* sensorId */, true /* hasEnrollments */); + mTestableLooper.processAllMessages(); + + // Nothing should crash. + } + + @Test public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception { - showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); + showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */); mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, null /* credentialAttestation */); verify(mReceiver).onDialogDismissed( @@ -497,7 +566,7 @@ public class AuthControllerTest extends SysuiTestCase { when(mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks); mAuthController.mTaskStackListener.onTaskStackChanged(); - waitForIdleSync(); + mTestableLooper.processAllMessages(); assertNull(mAuthController.mCurrentDialog); assertNull(mAuthController.mReceiver); @@ -528,7 +597,7 @@ public class AuthControllerTest extends SysuiTestCase { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mAuthController.mBroadcastReceiver.onReceive(mContext, intent); - waitForIdleSync(); + mTestableLooper.processAllMessages(); assertNull(mAuthController.mCurrentDialog); assertNull(mAuthController.mReceiver); @@ -598,6 +667,7 @@ public class AuthControllerTest extends SysuiTestCase { private PromptInfo mLastBiometricPromptInfo; TestableAuthController(Context context, + Execution execution, CommandQueue commandQueue, ActivityTaskManager activityTaskManager, WindowManager windowManager, @@ -605,7 +675,7 @@ public class AuthControllerTest extends SysuiTestCase { FaceManager faceManager, Provider<UdfpsController> udfpsControllerFactory, Provider<SidefpsController> sidefpsControllerFactory) { - super(context, commandQueue, activityTaskManager, windowManager, + super(context, execution, commandQueue, activityTaskManager, windowManager, fingerprintManager, faceManager, udfpsControllerFactory, sidefpsControllerFactory, mDisplayManager, mHandler); } 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 155b61891d12..cde99b4ba5d8 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -31,6 +31,7 @@ import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF; import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; +import static android.os.UserHandle.USER_CURRENT; import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; @@ -177,7 +178,7 @@ public class LocationProviderManager extends protected interface LocationTransport { void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) throws Exception; + @Nullable IRemoteCallback onCompleteCallback) throws Exception; void deliverOnFlushComplete(int requestCode) throws Exception; } @@ -197,9 +198,8 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) throws RemoteException { - mListener.onLocationChanged(locationResult.asList(), - SingleUseCallback.wrap(onCompleteCallback)); + @Nullable IRemoteCallback onCompleteCallback) throws RemoteException { + mListener.onLocationChanged(locationResult.asList(), onCompleteCallback); } @Override @@ -227,7 +227,7 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(LocationResult locationResult, - @Nullable Runnable onCompleteCallback) + @Nullable IRemoteCallback onCompleteCallback) throws PendingIntent.CanceledException { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setDontSendToRestrictedApps(true); @@ -243,20 +243,34 @@ public class LocationProviderManager extends intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0])); } + PendingIntent.OnFinished onFinished = null; + // 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 + // b/201299281 (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); + GatedCallback gatedCallback; + if (onCompleteCallback != null) { + gatedCallback = new GatedCallback(() -> { + try { + onCompleteCallback.sendResult(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + }); + onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run(); + } else { + gatedCallback = new GatedCallback(null); + } mPendingIntent.send( mContext, 0, intent, - (pI, i, rC, rD, rE) -> gatedCallback.run(), + onFinished, null, null, options.toBundle()); @@ -293,7 +307,7 @@ public class LocationProviderManager extends @Override public void deliverOnLocationChanged(@Nullable LocationResult locationResult, - @Nullable Runnable onCompleteCallback) + @Nullable IRemoteCallback onCompleteCallback) throws RemoteException { // ILocationCallback doesn't currently support completion callbacks Preconditions.checkState(onCompleteCallback == null); @@ -714,6 +728,13 @@ public class LocationProviderManager extends final PowerManager.WakeLock mWakeLock; + // b/206340085 - if we allocate a new wakelock releaser object for every delivery we + // increase the risk of resource starvation. if a client stops processing deliveries the + // system server binder allocation pool will be starved as we continue to queue up + // deliveries, each with a new allocation. in order to mitigate this, we use a single + // releaser object per registration rather than per delivery. + final ExternalWakeLockReleaser mWakeLockReleaser; + private volatile ProviderTransport mProviderTransport; private int mNumLocationsDelivered = 0; private long mExpirationRealtimeMs = Long.MAX_VALUE; @@ -727,6 +748,7 @@ public class LocationProviderManager extends .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); mWakeLock.setReferenceCounted(true); mWakeLock.setWorkSource(request.getWorkSource()); + mWakeLockReleaser = new ExternalWakeLockReleaser(identity, mWakeLock); } @Override @@ -872,6 +894,10 @@ public class LocationProviderManager extends MAX_FASTEST_INTERVAL_JITTER_MS); if (deltaMs < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) { + if (D) { + Log.v(TAG, mName + " provider registration " + getIdentity() + + " dropped delivery - too fast"); + } return false; } @@ -881,6 +907,10 @@ public class LocationProviderManager extends if (smallestDisplacementM > 0.0 && location.distanceTo( mPreviousLocation) <= smallestDisplacementM) { + if (D) { + Log.v(TAG, mName + " provider registration " + getIdentity() + + " dropped delivery - too close"); + } return false; } } @@ -898,7 +928,8 @@ public class LocationProviderManager extends if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(getPermissionLevel()), getIdentity())) { if (D) { - Log.w(TAG, "noteOp denied for " + getIdentity()); + Log.w(TAG, + mName + " provider registration " + getIdentity() + " noteOp denied"); } return null; } @@ -943,7 +974,7 @@ public class LocationProviderManager extends } listener.deliverOnLocationChanged(deliverLocationResult, - mUseWakeLock ? mWakeLock::release : null); + mUseWakeLock ? mWakeLockReleaser : null); EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(), getIdentity()); } @@ -1482,7 +1513,7 @@ public class LocationProviderManager extends public boolean isEnabled(int userId) { if (userId == UserHandle.USER_NULL) { return false; - } else if (userId == UserHandle.USER_CURRENT) { + } else if (userId == USER_CURRENT) { return isEnabled(mUserHelper.getCurrentUserId()); } @@ -1655,7 +1686,7 @@ public class LocationProviderManager extends } } return lastLocation; - } else if (userId == UserHandle.USER_CURRENT) { + } else if (userId == USER_CURRENT) { return getLastLocationUnsafe(mUserHelper.getCurrentUserId(), permissionLevel, isBypass, maximumAgeMs); } @@ -1700,7 +1731,7 @@ public class LocationProviderManager extends setLastLocation(location, runningUserIds[i]); } return; - } else if (userId == UserHandle.USER_CURRENT) { + } else if (userId == USER_CURRENT) { setLastLocation(location, mUserHelper.getCurrentUserId()); return; } @@ -2383,13 +2414,13 @@ public class LocationProviderManager extends filtered = locationResult.filter(location -> { if (!location.isMock()) { if (location.getLatitude() == 0 && location.getLongitude() == 0) { - Log.w(TAG, "blocking 0,0 location from " + mName + " provider"); + Log.e(TAG, "blocking 0,0 location from " + mName + " provider"); return false; } } if (!location.isComplete()) { - Log.w(TAG, "blocking incomplete location from " + mName + " provider"); + Log.e(TAG, "blocking incomplete location from " + mName + " provider"); return false; } @@ -2407,6 +2438,12 @@ public class LocationProviderManager extends filtered = locationResult; } + Location last = getLastLocationUnsafe(USER_CURRENT, PERMISSION_FINE, true, Long.MAX_VALUE); + if (last != null && locationResult.get(0).getElapsedRealtimeNanos() + < last.getElapsedRealtimeNanos()) { + Log.e(TAG, "non-monotonic location received from " + mName + " provider"); + } + // update last location setLastLocation(filtered.getLastLocation(), UserHandle.USER_ALL); @@ -2761,7 +2798,7 @@ public class LocationProviderManager extends @GuardedBy("this") private boolean mRun; - GatedCallback(Runnable callback) { + GatedCallback(@Nullable Runnable callback) { mCallback = callback; } @@ -2796,4 +2833,24 @@ public class LocationProviderManager extends } } } + + private static class ExternalWakeLockReleaser extends IRemoteCallback.Stub { + + private final CallerIdentity mIdentity; + private final PowerManager.WakeLock mWakeLock; + + ExternalWakeLockReleaser(CallerIdentity identity, PowerManager.WakeLock wakeLock) { + mIdentity = identity; + mWakeLock = Objects.requireNonNull(wakeLock); + } + + @Override + public void sendResult(Bundle data) { + try { + mWakeLock.release(); + } catch (RuntimeException e) { + Log.e(TAG, "wakelock over-released by " + mIdentity, e); + } + } + } } diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java index 22a675ad39ab..5e38bca78a7c 100644 --- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java @@ -23,6 +23,8 @@ import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG; +import static java.lang.Math.max; + import android.annotation.Nullable; import android.location.Location; import android.location.LocationResult; @@ -53,6 +55,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener { private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000; + private static final long MIN_INTERVAL_MS = 1000; final Object mLock = new Object(); @@ -179,7 +182,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation && mLastLocation != null && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs) <= MAX_STATIONARY_LOCATION_AGE_MS) { - throttlingIntervalMs = mIncomingRequest.getIntervalMillis(); + throttlingIntervalMs = max(mIncomingRequest.getIntervalMillis(), MIN_INTERVAL_MS); } ProviderRequest newRequest; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java index 4d6f49e5d223..4eba21934a4e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java @@ -90,6 +90,19 @@ public class StationaryThrottlingLocationProviderTest { } @Test + public void testThrottle_lowInterval() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(0).build(); + + mProvider.getController().setRequest(request); + mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom)); + verify(mListener, times(1)).onReportLocation(any(LocationResult.class)); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mListener, after(1500).times(2)).onReportLocation(any(LocationResult.class)); + } + + @Test public void testThrottle_stationaryExit() { ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); @@ -104,17 +117,16 @@ public class StationaryThrottlingLocationProviderTest { mInjector.getDeviceIdleHelper().setIdle(true); verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST); - verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); - verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class)); mInjector.getDeviceStationaryHelper().setStationary(false); verify(mDelegate, times(2)).onSetRequest(request); - verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class)); } @Test public void testThrottle_idleExit() { - ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build(); mProvider.getController().setRequest(request); verify(mDelegate).onSetRequest(request); @@ -127,17 +139,16 @@ public class StationaryThrottlingLocationProviderTest { mInjector.getDeviceStationaryHelper().setStationary(true); verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST); - verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); - verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class)); mInjector.getDeviceIdleHelper().setIdle(false); verify(mDelegate, times(2)).onSetRequest(request); - verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class)); + verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class)); } @Test public void testThrottle_NoInitialLocation() { - ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build(); mProvider.getController().setRequest(request); verify(mDelegate).onSetRequest(request); @@ -149,11 +160,11 @@ public class StationaryThrottlingLocationProviderTest { mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom)); verify(mListener, times(1)).onReportLocation(any(LocationResult.class)); verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST); - verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); + verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class)); mInjector.getDeviceStationaryHelper().setStationary(false); verify(mDelegate, times(2)).onSetRequest(request); - verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class)); + verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class)); } @Test |