Merge "[flexiglass] Notification section in pure Compose." into main
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3ab889d..665d8d2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -557,13 +557,15 @@
* on a particular SessionConfiguration.</p>
*
* @return List of CameraCharacteristic keys containing characterisitics specific to a session
- * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+ * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
+ * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
*/
@NonNull
@FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
if (mAvailableSessionCharacteristicsKeys == null) {
- mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+ mAvailableSessionCharacteristicsKeys =
+ Arrays.asList(CONTROL_ZOOM_RATIO_RANGE, SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
}
return mAvailableSessionCharacteristicsKeys;
}
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 8423000..67f4775 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,8 +19,11 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -37,6 +40,23 @@
* @hide
*/
public final class LocationResult implements Parcelable {
+ private static final String TAG = "LocationResult";
+
+ // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
+ // could likely be lower, but we only want to throw out really absurd values.
+ private static final float MAX_ACCURACY_M = 1000000;
+
+ // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
+ // current fastest private jet). Higher speed than the value is considered as a malfunction
+ // than a correct reading.
+ private static final float MAX_SPEED_MPS = 343;
+
+ /** Exception representing an invalid location within a {@link LocationResult}. */
+ public static class BadLocationException extends Exception {
+ public BadLocationException(String message) {
+ super(message);
+ }
+ }
/**
* Creates a new LocationResult from the given locations, making a copy of each location.
@@ -101,18 +121,60 @@
*
* @hide
*/
- public @NonNull LocationResult validate() {
+ public @NonNull LocationResult validate() throws BadLocationException {
long prevElapsedRealtimeNs = 0;
final int size = mLocations.size();
for (int i = 0; i < size; ++i) {
Location location = mLocations.get(i);
- if (!location.isComplete()) {
- throw new IllegalArgumentException(
- "incomplete location at index " + i + ": " + mLocations);
- }
- if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
- throw new IllegalArgumentException(
- "incorrectly ordered location at index " + i + ": " + mLocations);
+ if (Flags.locationValidation()) {
+ if (location.getLatitude() < -90.0
+ || location.getLatitude() > 90.0
+ || location.getLongitude() < -180.0
+ || location.getLongitude() > 180.0
+ || Double.isNaN(location.getLatitude())
+ || Double.isNaN(location.getLongitude())) {
+ throw new BadLocationException("location must have valid lat/lng");
+ }
+ if (!location.hasAccuracy()) {
+ throw new BadLocationException("location must have accuracy");
+ }
+ if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
+ throw new BadLocationException("location must have reasonable accuracy");
+ }
+ if (location.getTime() < 0) {
+ throw new BadLocationException("location must have valid time");
+ }
+ if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
+ throw new BadLocationException(
+ "location must have valid monotonically increasing realtime");
+ }
+ if (location.getElapsedRealtimeNanos()
+ > SystemClock.elapsedRealtimeNanos()) {
+ throw new BadLocationException("location must not have realtime in the future");
+ }
+ if (!location.isMock()) {
+ if (location.getProvider() == null) {
+ throw new BadLocationException("location must have valid provider");
+ }
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ throw new BadLocationException("location must not be at 0,0");
+ }
+ }
+
+ if (location.hasSpeed() && (location.getSpeed() < 0
+ || location.getSpeed() > MAX_SPEED_MPS)) {
+ Log.w(TAG, "removed bad location speed: " + location.getSpeed());
+ location.removeSpeed();
+ }
+ } else {
+ if (!location.isComplete()) {
+ throw new IllegalArgumentException(
+ "incomplete location at index " + i + ": " + mLocations);
+ }
+ if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
+ throw new IllegalArgumentException(
+ "incorrectly ordered location at index " + i + ": " + mLocations);
+ }
}
prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
}
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index f4b1056..a8464d3 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -27,3 +27,10 @@
description: "Flag for releasing SUPL connection on timeout"
bug: "315024652"
}
+
+flag {
+ name: "location_validation"
+ namespace: "location"
+ description: "Flag for location validation"
+ bug: "314328533"
+}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3da52cc..7f95886 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -77,3 +77,9 @@
bug: "279555229"
}
+flag {
+ name: "enable_notifying_activity_manager_with_media_session_status_change"
+ namespace: "media_solutions"
+ description: "Notify ActivityManager with the changes in playback state of the media session."
+ bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6..60497fe 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@
return false;
}
+ /**
+ * Returns whether the service holding the media session should run in the foreground when the
+ * media session has this playback state or not.
+ *
+ * @hide
+ */
+ public boolean shouldAllowServiceToRunInForeground() {
+ switch (mState) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 96e95fd..3fcb871 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,8 @@
mpuSequenceNumber, isPesPrivateData, sc,
audioDescriptor.get(), presentationsJObj.get()));
+ // Protect mFilterClient from being set to null.
+ android::Mutex::Autolock autoLock(mLock);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -939,38 +941,52 @@
}
}
}
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterEventID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterEventID;
+
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(mFilterObj, nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, array.get());
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterEvent:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterEventID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterEventID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, array.get());
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
ALOGV("FilterClientCallbackImpl::onFilterStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterStatusID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterStatusID;
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(filter.get(), nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterStatus:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterStatus:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterStatusID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterStatusID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
}
void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = false;
@@ -979,6 +995,7 @@
void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = true;
@@ -1047,11 +1064,14 @@
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (mFilterObj != nullptr) {
- env->DeleteWeakGlobalRef(mFilterObj);
- mFilterObj = nullptr;
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (mFilterObj != nullptr) {
+ env->DeleteWeakGlobalRef(mFilterObj);
+ mFilterObj = nullptr;
+ }
+ mFilterClient = nullptr;
}
- mFilterClient = nullptr;
env->DeleteGlobalRef(mEventClass);
env->DeleteGlobalRef(mSectionEventClass);
env->DeleteGlobalRef(mMediaEventClass);
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 01c998d..3de3ab9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -136,6 +136,7 @@
private:
jweak mFilterObj;
sp<FilterClient> mFilterClient;
+ android::Mutex mLock;
jclass mEventClass;
jclass mSectionEventClass;
jclass mMediaEventClass;
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 26da1f0..4b21105 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -47,7 +47,7 @@
suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
withContext(backgroundDispatcher) {
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = if (enabled) 1 else 0,
)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 7ef16a8..754d5dc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -37,15 +37,17 @@
): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun set(
+ suspend fun setInt(
name: String,
value: Int,
)
- suspend fun get(
+ suspend fun getInt(
name: String,
defaultValue: Int = 0,
): Int
+
+ suspend fun getString(name: String): String?
}
class SecureSettingsRepositoryImpl(
@@ -80,7 +82,7 @@
.flowOn(backgroundDispatcher)
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
withContext(backgroundDispatcher) {
Settings.Secure.putInt(
contentResolver,
@@ -90,7 +92,7 @@
}
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return withContext(backgroundDispatcher) {
Settings.Secure.getInt(
contentResolver,
@@ -99,4 +101,13 @@
)
}
}
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getString(
+ contentResolver,
+ name,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 1c86a07..37b9792 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,11 +28,15 @@
return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return settings.value[name]?.toInt() ?: defaultValue
}
+
+ override suspend fun getString(name: String): String? {
+ return settings.value[name]
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361..cfa5294 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@
SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
}
}
+
+ /** The arrow navigation that was operating the slider has stopped. */
+ fun onArrowUp() {
+ _currentEvent.update { previousEvent ->
+ SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63..10098fa 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@
override suspend fun iterateState(event: SliderEvent) {
when (currentState) {
- SliderState.IDLE -> handleIdle(event.type)
+ SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
SliderState.WAIT -> handleWait(event.type, event.currentProgress)
SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@
SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+ handleArrowContinuous(event.type, event.currentProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
}
latestProgress = event.currentProgress
}
- private fun handleIdle(newEventType: SliderEventType) {
+ private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
timerJob = launchTimer()
// The WAIT state will wait for the timer to complete or a slider progress to occur.
// This will disambiguate between an imprecise touch that acquires the slider handle,
// and a select and jump operation in the slider track.
setState(SliderState.WAIT)
+ } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+ val state =
+ if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVED_ONCE
+ setState(state)
}
}
@@ -176,6 +185,13 @@
SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
SliderState.JUMP_TRACK_LOCATION_SELECTED ->
sliderListener.onProgressJump(latestProgress)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+ executeOnBookend()
+ // This transitory execution must also reset the state
+ resetState()
+ }
else -> {}
}
}
@@ -204,6 +220,43 @@
currentProgress <= config.lowerBookendThreshold
}
+ private fun handleArrowOnce(newEventType: SliderEventType) {
+ val nextState =
+ when (newEventType) {
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+ val reachedBookend = bookendReached(currentProgress)
+ val nextState =
+ when (newEventType) {
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+ if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowBookend() = setState(SliderState.IDLE)
+
@VisibleForTesting
fun setState(state: SliderState) {
currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e277..4a63941 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@
/* The slider has stopped tracking touch events. */
STOPPED_TRACKING_TOUCH,
/* The external (not touch) stimulus that was modifying the slider progress has stopped. */
- EXTERNAL_STIMULUS_RELEASE,
+ ARROW_UP,
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e6..de6ddd7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@
DRAG_HANDLE_REACHED_BOOKEND,
/* A location in the slider track has been selected. */
JUMP_TRACK_LOCATION_SELECTED,
- /* The slider handled moved to a bookend after it was selected. */
+ /* The slider handle moved to a bookend after it was selected. */
JUMP_BOOKEND_SELECTED,
+ /** The slider handle moved due to single select-and-arrow operation */
+ ARROW_HANDLE_MOVED_ONCE,
+ /** The slider handle moves continuously due to constant select-and-arrow operations */
+ ARROW_HANDLE_MOVES_CONTINUOUSLY,
+ /** The slider handle reached a bookend due to a select-and-arrow operation */
+ ARROW_HANDLE_REACHED_BOOKEND,
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index bc5090f..be1fa2b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@
mListener.onChanged(mTracking, progress, false);
SeekableSliderEventProducer eventProducer =
mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null) {
+ if (eventProducer != null && fromUser) {
eventProducer.onProgressChanged(seekBar, progress, fromUser);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd..c22d35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@
assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
}
+
+ @Test
+ fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onStartTrackingTouch(seekBar)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+ }
+
+ @Test
+ fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, false)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e49..db04962 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@
verifyNoMoreInteractions(sliderStateListener)
}
+ @Test
+ fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ initTracker(testScheduler)
+
+ // GIVEN a progress due to an external source that lands at the middle of the slider
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+ verify(sliderStateListener).onSelectAndArrow(progress)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // GIVEN a progress due to an external source that lands at the upper bookend
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving back to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // WHEN a progress is recorded due to an external source that lands at the lower bookend
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves back to IDLE and there are no haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider gets an external progress change
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves to IDLE and no haptics are played
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves to WAIT and the wait job starts.
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider changes progress programmatically at the middle
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker stays in the same state and haptics are delivered appropriately
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
private fun initTracker(
scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 80f8cf1..50349be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -58,13 +58,13 @@
testScope.runTest {
val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 1,
)
assertThat(showNotifs).isEqualTo(true)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 0,
)
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cac2efb..08093c0 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1463,4 +1463,9 @@
*/
@NonNull
public abstract PackageArchiver getPackageArchiver();
+
+ /**
+ * Returns true if the device is upgrading from an SDK version lower than the one specified.
+ */
+ public abstract boolean isUpgradingFromLowerThan(int sdkVersion);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d461643..96b1650 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -303,6 +303,23 @@
@Retention(RetentionPolicy.SOURCE)
@interface FgsStopReason {}
+ /**
+ * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
+ * except:
+ * <ul>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}</li>
+ * </ul>
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -1053,6 +1070,20 @@
}
}
+ private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
+ @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
+ r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
+ : REASON_DENIED;
+ if (Flags.fgsBootCompleted()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
+ && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
+ // Filter through types
+ return ((foregroundServiceType & mAm.mConstants.FGS_BOOT_COMPLETED_ALLOWLIST) != 0);
+ }
+ // Not BOOT_COMPLETED
+ return true;
+ }
+
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
int callingUid, int callingPid, String callingProcessName,
int callingProcessState, boolean fgRequired, boolean callerFg,
@@ -2087,6 +2118,11 @@
// anyway, so we just remove the SHORT_SERVICE type.
foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
+ if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+ throw new ForegroundServiceStartNotAllowedException("FGS type "
+ + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+ + " not allowed to start from BOOT_COMPLETED!");
+ }
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 1d69905..1c3c21c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,12 @@
package com.android.server.am;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
@@ -73,6 +79,9 @@
= "fgservice_screen_on_before_time";
private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
= "fgservice_screen_on_after_time";
+
+ private static final String KEY_FGS_BOOT_COMPLETED_ALLOWLIST = "fgs_boot_completed_allowlist";
+
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -166,6 +175,15 @@
private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
+
+ private static final int DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST =
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
+ | FOREGROUND_SERVICE_TYPE_HEALTH
+ | FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ | FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+ | FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ | FOREGROUND_SERVICE_TYPE_LOCATION;
+
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -446,6 +464,9 @@
// on until we will stop reporting it.
public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
+ // Allow-list for FGS types that are allowed to start from BOOT_COMPLETED.
+ public int FGS_BOOT_COMPLETED_ALLOWLIST = DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST;
+
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
// to avoid thrashing of provider processes under low memory situations.
@@ -1450,6 +1471,8 @@
DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
+ FGS_BOOT_COMPLETED_ALLOWLIST = mParser.getInt(KEY_FGS_BOOT_COMPLETED_ALLOWLIST,
+ DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -2091,6 +2114,8 @@
pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
+ pw.print(" "); pw.print(KEY_FGS_BOOT_COMPLETED_ALLOWLIST); pw.print("=");
+ pw.println(FGS_BOOT_COMPLETED_ALLOWLIST);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 00dd169..848a2b0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -30,6 +30,7 @@
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Process.INVALID_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
@@ -555,6 +556,13 @@
} else if (opt.equals("--dismiss-keyguard-if-insecure")
|| opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
+ } else if (opt.equals("--allow-fgs-start-reason")) {
+ final int reasonCode = Integer.parseInt(getNextArgRequired());
+ mBroadcastOptions = BroadcastOptions.makeBasic();
+ mBroadcastOptions.setTemporaryAppAllowlist(10_000,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ reasonCode,
+ "");
} else {
return false;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index cc56110..d0d647c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -179,6 +179,7 @@
"text",
"threadnetwork",
"tv_system_ui",
+ "usb",
"vibrator",
"virtual_devices",
"wallet_integration",
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 27b01a5..9c4225d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.flags.Flags;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -1380,7 +1381,11 @@
location.setExtras(mLocationExtras.getBundle());
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
if (mStarted) {
mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1751,7 +1756,11 @@
}
}
- reportLocation(LocationResult.wrap(locations).validate());
+ try {
+ reportLocation(LocationResult.wrap(locations).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
Runnable[] listeners;
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 91e6a80..7d44aec 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.altitude.AltitudeConverter;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
@@ -910,7 +911,8 @@
< getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
if (D) {
Log.v(TAG, mName + " provider registration " + getIdentity()
- + " dropped delivery - too fast");
+ + " dropped delivery - too fast (deltaMs="
+ + deltaMs + ").");
}
return false;
}
@@ -2574,29 +2576,17 @@
@GuardedBy("mMultiplexerLock")
@Nullable
private LocationResult processReportedLocation(LocationResult locationResult) {
- LocationResult processed = locationResult.filter(location -> {
- if (!location.isMock()) {
- if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
- return false;
- }
- }
-
- if (!location.isComplete()) {
- Log.e(TAG, "blocking incomplete location from " + mName + " provider");
- return false;
- }
-
- return true;
- });
- if (processed == null) {
+ try {
+ locationResult.validate();
+ } catch (BadLocationException e) {
+ Log.e(TAG, "Dropping invalid locations: " + e);
return null;
}
// Attempt to add a missing MSL altitude on behalf of the provider.
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
"enable_location_provider_manager_msl", true)) {
- return processed.map(location -> {
+ return locationResult.map(location -> {
if (!location.hasMslAltitude() && location.hasAltitude()) {
try {
Location locationCopy = new Location(location);
@@ -2626,7 +2616,7 @@
return location;
});
}
- return processed;
+ return locationResult;
}
@GuardedBy("mMultiplexerLock")
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 52b04d4..4efacd7 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -55,7 +56,11 @@
Location location = new Location(l);
location.setIsFromMockProvider(true);
mLocation = location;
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 05966da..a597edd 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -305,7 +305,7 @@
return;
}
- reportLocation(LocationResult.wrap(location).validate());
+ reportLocation(LocationResult.wrap(location));
}
}
@@ -316,8 +316,7 @@
if (mProxy != this) {
return;
}
-
- reportLocation(LocationResult.wrap(locations).validate());
+ reportLocation(LocationResult.wrap(locations));
}
}
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c20..07b333a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
@@ -89,6 +90,12 @@
}
@Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ // TODO: Implement when MediaSession2 knows about its owner pid.
+ return null;
+ }
+
+ @Override
public boolean isSystemPriority() {
// System priority session is currently only allowed for telephony, so it's OK to stick to
// the media1 API at this moment.
@@ -217,7 +224,8 @@
synchronized (mLock) {
service = mService;
}
- service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+ service.onSessionPlaybackStateChanged(
+ MediaSession2Record.this, playbackActive, /* playbackState= */ null);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca..cce66e2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
+ private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
private final Object mLock = new Object();
private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
+ mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
+ private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+ return new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(mOwnerPid)
+ .setClientUid(getUid())
+ .setClientPackageName(getPackageName())
+ .setClientAppThread(null)
+ .setSticky(false)
+ .setClientInstanceName(
+ "MediaSessionFgsDelegate_"
+ + getUid()
+ + "_"
+ + mOwnerPid
+ + "_"
+ + getPackageName())
+ .setForegroundServiceTypes(0)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+ .build();
+ }
+
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -681,6 +706,11 @@
return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
}
+ @Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ return mForegroundServiceDelegationOptions;
+ }
+
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@
final long token = Binder.clearCallingIdentity();
try {
mService.onSessionPlaybackStateChanged(
- MediaSessionRecord.this, shouldUpdatePriority);
+ MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02..99c8ea9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.AudioManager;
+import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -51,6 +53,15 @@
int getUserId();
/**
+ * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+ * service with changes in the {@link PlaybackState} for this session.
+ *
+ * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+ * manager service with changes in the {@link PlaybackState} for this session.
+ */
+ ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+ /**
* Check if this session has system priority and should receive media buttons before any other
* sessions.
*
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c59511..d4666ba 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -59,6 +61,7 @@
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -144,6 +147,7 @@
private AudioManager mAudioManager;
private boolean mHasFeatureLeanback;
private ActivityManagerLocal mActivityManagerLocal;
+ private ActivityManagerInternal mActivityManagerInternal;
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -229,6 +233,7 @@
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
@Override
@@ -285,11 +290,31 @@
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
-
+ notifyActivityManagerWithActiveStateChanges(record, record.isActive());
mHandler.postSessionsChanged(record);
}
}
+ private void notifyActivityManagerWithActiveStateChanges(
+ MediaSessionRecordImpl record, boolean isActive) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (isActive) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ }
+ }
+
// Currently only media1 can become global priority session.
void setGlobalPrioritySession(MediaSessionRecord record) {
synchronized (mLock) {
@@ -371,8 +396,10 @@
}
}
- void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
- boolean shouldUpdatePriority) {
+ void onSessionPlaybackStateChanged(
+ MediaSessionRecordImpl record,
+ boolean shouldUpdatePriority,
+ @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +407,27 @@
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+ notifyActivityManagerWithPlaybackStateChanges(record, playbackState);
+ }
+ }
+
+ private void notifyActivityManagerWithPlaybackStateChanges(
+ MediaSessionRecordImpl record, PlaybackState playbackState) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null || playbackState == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (playbackState.shouldAllowServiceToRunInForeground()) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
}
}
@@ -543,9 +591,23 @@
}
session.close();
+ notifyActivityManagerWithSessionDestroyed(session);
mHandler.postSessionsChanged(session);
}
+ private void notifyActivityManagerWithSessionDestroyed(MediaSessionRecordImpl record) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ mActivityManagerInternal.stopForegroundServiceDelegate(foregroundServiceDelegationOptions);
+ }
+
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 81d5d81..b7deef0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -607,6 +607,8 @@
private final boolean mIsUpgrade;
private final boolean mIsPreNMR1Upgrade;
private final boolean mIsPreQUpgrade;
+ // If mIsUpgrade == true, contains the prior SDK version, else -1.
+ private final int mPriorSdkVersion;
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@@ -1891,6 +1893,7 @@
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
+ mPriorSdkVersion = testParams.priorSdkVersion;
mIsUpgrade = testParams.isUpgrade;
mMetrics = testParams.Metrics;
mModuleInfoProvider = testParams.moduleInfoProvider;
@@ -2230,7 +2233,7 @@
"Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
+ PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
}
-
+ mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
mInjector.getSystemPartitions());
@@ -7099,6 +7102,12 @@
mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
visibilityAllowList, mHandler);
}
+
+ @Override
+ public boolean isUpgradingFromLowerThan(int sdkVersion) {
+ final boolean isUpgrading = mPriorSdkVersion != -1;
+ return isUpgrading && mPriorSdkVersion < sdkVersion;
+ }
}
private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 86d78dc..2d79718 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -65,6 +65,7 @@
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
public boolean isPreQupgrade;
+ public int priorSdkVersion = -1;
public boolean isUpgrade;
public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
public DisplayMetrics Metrics;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7..1185a4e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@
// AdServicesManagerService (PP API service)
t.traceBegin("StartAdServicesManagerService");
- SystemService adServices = mSystemServiceManager
- .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
- if (adServices instanceof Dumpable) {
- mDumper.addDumpable((Dumpable) adServices);
- }
+ mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
t.traceEnd();
// OnDevicePersonalizationSystemService
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 293003d..32878b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -67,6 +67,7 @@
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -78,8 +79,10 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;
@@ -97,6 +100,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -140,6 +144,9 @@
private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
private static final String MISSING_PERMISSION = "missing_permission";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Random mRandom;
@Mock
@@ -1347,6 +1354,24 @@
assertThat(mManager.isVisibleToCaller()).isFalse();
}
+ @Test
+ public void testValidateLocation_futureLocation() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LOCATION_VALIDATION);
+ Location location = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(location);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+
+ Location futureLocation = createLocation(NAME, mRandom);
+ futureLocation.setElapsedRealtimeNanos(
+ SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(2));
+ mProvider.setProviderLocation(futureLocation);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {