summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/text/style/StyleSpan.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java135
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java88
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java91
-rw-r--r--services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java31
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