summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java6
-rw-r--r--core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl30
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java16
-rw-r--r--core/java/android/service/voice/SoftwareHotwordDetector.java15
-rw-r--r--core/java/android/service/voice/SoundTriggerFailure.java21
-rw-r--r--core/java/android/service/voice/VisualQueryDetector.java11
-rw-r--r--core/java/android/util/TimeUtils.java9
-rw-r--r--core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl17
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerDetector.java43
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt14
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt12
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt37
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt14
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt5
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt27
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt300
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt176
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt25
-rw-r--r--services/core/java/com/android/server/RescueParty.java8
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java169
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java66
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java2
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java21
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java11
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java27
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java320
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java24
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java6
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaDevice.java256
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java3
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java147
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java62
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java28
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java28
66 files changed, 1806 insertions, 789 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index aef9dd058658..3aec8ba39a35 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1588,12 +1588,12 @@ public class JobSchedulerService extends com.android.server.SystemService
final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>();
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
- // Write out for loop to avoid addAll() creating an Iterator.
+ // Write out for loop to avoid creating an Iterator.
for (int i = jobs.size() - 1; i >= 0; i--) {
final JobStatus job = jobs.valueAt(i);
List<JobInfo> outList = outMap.get(job.getNamespace());
if (outList == null) {
- outList = new ArrayList<JobInfo>(jobs.size());
+ outList = new ArrayList<>();
outMap.put(job.getNamespace(), outList);
}
@@ -1606,7 +1606,7 @@ public class JobSchedulerService extends com.android.server.SystemService
private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) {
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
- ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+ ArrayList<JobInfo> outList = new ArrayList<>();
// Write out for loop to avoid addAll() creating an Iterator.
for (int i = jobs.size() - 1; i >= 0; i--) {
final JobStatus job = jobs.valueAt(i);
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index dcc336966810..466373030c78 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -42,12 +42,6 @@ oneway interface IRecognitionStatusCallback {
void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent);
/**
- * Called when the detection fails due to an error.
- *
- * @param status The error code that was seen.
- */
- void onError(int status);
- /**
* Called when the recognition is paused temporarily for some reason.
*/
void onRecognitionPaused();
@@ -55,4 +49,28 @@ oneway interface IRecognitionStatusCallback {
* Called when the recognition is resumed after it was temporarily paused.
*/
void onRecognitionResumed();
+
+ // Error callbacks to follow
+ /**
+ * Called when this recognition has been preempted by another.
+ */
+ void onPreempted();
+
+ /**
+ * Called when the underlying ST module service has died.
+ */
+ void onModuleDied();
+
+ /**
+ * Called when the service failed to gracefully resume recognition following a pause.
+ * @param status - The received error code.
+ */
+ void onResumeFailed(int status);
+
+ /**
+ * Called when the service failed to pause recognition when required.
+ * TODO(b/276507281) Remove. This should never happen, so we should abort instead.
+ * @param status - The received error code.
+ */
+ void onPauseFailed(int status);
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 8688a18880b7..24c96eae03cb 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1573,16 +1573,6 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
}
@Override
- public void onError(int status) {
- Slog.i(TAG, "onError: " + status);
- // TODO(b/271534248): This is a workaround before the sound trigger uses the new error
- // method.
- Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
- new SoundTriggerFailure(SoundTriggerFailure.ERROR_CODE_UNKNOWN,
- "Sound trigger error")).sendToTarget();
- }
-
- @Override
public void onHotwordDetectionServiceFailure(
HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
@@ -1605,6 +1595,12 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
}
@Override
+ public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+ Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
+ Objects.requireNonNull(soundTriggerFailure)).sendToTarget();
+ }
+
+ @Override
public void onUnknownFailure(String errorMessage) throws RemoteException {
Slog.v(TAG, "onUnknownFailure: " + errorMessage);
Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index eac7aee43859..7ab4fafcf312 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -234,14 +234,6 @@ class SoftwareHotwordDetector extends AbstractDetector {
}
@Override
- public void onError(int status) throws RemoteException {
- if (DEBUG) {
- Slog.i(TAG, "Ignored #onError (" + status + ") event");
- }
- // TODO: Check if we still need to implement this method with DetectorFailure mechanism.
- }
-
- @Override
public void onHotwordDetectionServiceFailure(
HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
throws RemoteException {
@@ -265,6 +257,13 @@ class SoftwareHotwordDetector extends AbstractDetector {
}
@Override
+ public void onSoundTriggerFailure(SoundTriggerFailure onSoundTriggerFailure)
+ throws RemoteException {
+ // It should never be called here.
+ Slog.wtf(TAG, "Unexpected STFailure in software detector: " + onSoundTriggerFailure);
+ }
+
+ @Override
public void onUnknownFailure(String errorMessage) throws RemoteException {
Slog.v(TAG, "onUnknownFailure: " + errorMessage);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/SoundTriggerFailure.java b/core/java/android/service/voice/SoundTriggerFailure.java
index 5560800a373f..2ce5e5da4724 100644
--- a/core/java/android/service/voice/SoundTriggerFailure.java
+++ b/core/java/android/service/voice/SoundTriggerFailure.java
@@ -73,18 +73,28 @@ public final class SoundTriggerFailure implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface SoundTriggerErrorCode {}
- private int mErrorCode = ERROR_CODE_UNKNOWN;
- private String mErrorMessage = "Unknown";
+ private final int mErrorCode;
+ private final String mErrorMessage;
/**
* @hide
*/
@TestApi
- public SoundTriggerFailure(int errorCode, @NonNull String errorMessage) {
+ public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode,
+ @NonNull String errorMessage) {
if (TextUtils.isEmpty(errorMessage)) {
throw new IllegalArgumentException("errorMessage is empty or null.");
}
- mErrorCode = errorCode;
+ switch (errorCode) {
+ case ERROR_CODE_UNKNOWN:
+ case ERROR_CODE_MODULE_DIED:
+ case ERROR_CODE_RECOGNITION_RESUME_FAILED:
+ case ERROR_CODE_UNEXPECTED_PREEMPTION:
+ mErrorCode = errorCode;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode);
+ }
mErrorMessage = errorMessage;
}
@@ -110,13 +120,14 @@ public final class SoundTriggerFailure implements Parcelable {
@FailureSuggestedAction.FailureSuggestedActionDef
public int getSuggestedAction() {
switch (mErrorCode) {
+ case ERROR_CODE_UNKNOWN:
case ERROR_CODE_MODULE_DIED:
case ERROR_CODE_UNEXPECTED_PREEMPTION:
return FailureSuggestedAction.RECREATE_DETECTOR;
case ERROR_CODE_RECOGNITION_RESUME_FAILED:
return FailureSuggestedAction.RESTART_RECOGNITION;
default:
- return FailureSuggestedAction.NONE;
+ throw new AssertionError("Unexpected error code");
}
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b4f5ff1046ae..93b7964705ba 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -391,12 +391,6 @@ public class VisualQueryDetector {
}
@Override
- public void onError(int status) throws RemoteException {
- Slog.v(TAG, "Initialization Error: (" + status + ")");
- // Do nothing
- }
-
- @Override
public void onHotwordDetectionServiceFailure(
HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
throws RemoteException {
@@ -420,6 +414,11 @@ public class VisualQueryDetector {
}
@Override
+ public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+ Slog.wtf(TAG, "Unexpected STFailure in VisualQueryDetector" + soundTriggerFailure);
+ }
+
+ @Override
public void onUnknownFailure(String errorMessage) throws RemoteException {
Slog.v(TAG, "onUnknownFailure: " + errorMessage);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 9a93e1b9e1f9..d06b0ce1a2d8 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -354,6 +354,15 @@ public class TimeUtils {
}
/** @hide Just for debugging; not internationalized. */
+ public static void formatDuration(long time, long now, StringBuilder sb) {
+ if (time == 0) {
+ sb.append("--");
+ return;
+ }
+ formatDuration(time-now, sb, 0);
+ }
+
+ /** @hide Just for debugging; not internationalized. */
public static void formatDuration(long time, long now, PrintWriter pw) {
if (time == 0) {
pw.print("--");
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index ad0d1a401991..380118846dc7 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -20,6 +20,7 @@ import android.hardware.soundtrigger.SoundTrigger;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordRejectedResult;
+import android.service.voice.SoundTriggerFailure;
import android.service.voice.VisualQueryDetectionServiceFailure;
/**
@@ -57,13 +58,6 @@ oneway interface IHotwordRecognitionStatusCallback {
void onRejected(in HotwordRejectedResult result);
/**
- * Called when the detection fails due to an error.
- *
- * @param status The error code that was seen.
- */
- void onError(int status);
-
- /**
* Called when the detection fails due to an error occurs in the
* {@link HotwordDetectionService}.
*
@@ -84,6 +78,15 @@ oneway interface IHotwordRecognitionStatusCallback {
in VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure);
/**
+ * Called when the detection fails due to an error occurs in the
+ * {@link com.android.server.soundtrigger.SoundTriggerService}.
+ *
+ * @param soundTriggerFailure It provides the error code, error message and
+ * suggested action.
+ */
+ void onSoundTriggerFailure(in SoundTriggerFailure soundTriggerFailure);
+
+ /**
* Called when the detection fails due to an unknown error occurs.
*
* @param errorMessage It provides the error message.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e4d74b5373e0..9f10ae6066f5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1373,6 +1373,9 @@
<!-- Number of notifications to keep in the notification service historical archive -->
<integer name="config_notificationServiceArchiveSize">100</integer>
+ <!-- List of packages that will be able to use full screen intent in notifications by default -->
+ <string-array name="config_useFullScreenIntentPackages" translatable="false" />
+
<!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c6c1c8f120d7..a823d1fd8ff4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2017,6 +2017,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+ <java-symbol type="array" name="config_useFullScreenIntentPackages" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
<java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 1a3e54d54ee7..afa0a3271906 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -391,8 +391,26 @@ public final class SoundTriggerDetector {
* @hide
*/
@Override
- public void onError(int status) {
- Slog.d(TAG, "onError()" + status);
+ public void onRecognitionPaused() {
+ Slog.d(TAG, "onRecognitionPaused()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onRecognitionResumed() {
+ Slog.d(TAG, "onRecognitionResumed()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onPreempted() {
+ Slog.d(TAG, "onPreempted()");
mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
}
@@ -400,18 +418,27 @@ public final class SoundTriggerDetector {
* @hide
*/
@Override
- public void onRecognitionPaused() {
- Slog.d(TAG, "onRecognitionPaused()");
- mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
+ public void onModuleDied() {
+ Slog.d(TAG, "onModuleDied()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
}
/**
* @hide
*/
@Override
- public void onRecognitionResumed() {
- Slog.d(TAG, "onRecognitionResumed()");
- mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+ public void onResumeFailed(int status) {
+ Slog.d(TAG, "onResumeFailed()" + status);
+ mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onPauseFailed(int status) {
+ Slog.d(TAG, "onPauseFailed()" + status);
+ mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index e53e956c317c..a9bee039264e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -107,7 +107,7 @@ class CredentialManagerRepo(
initialUiState = when (requestInfo?.type) {
RequestInfo.TYPE_CREATE -> {
- val defaultProviderId = userConfigRepo.getDefaultProviderId()
+ val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
@@ -115,12 +115,14 @@ class CredentialManagerRepo(
getCreateRequestDisplayInfoInitialUiState(originName)!!
UiState(
createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
- providerEnableListUiState,
- providerDisableListUiState,
- defaultProviderId,
- requestDisplayInfoUiState,
+ enabledProviders = providerEnableListUiState,
+ disabledProviders = providerDisableListUiState,
+ defaultProviderIdPreferredByApp =
+ requestDisplayInfoUiState.appPreferredDefaultProviderId,
+ defaultProviderIdSetByUser = defaultProviderIdSetByUser,
+ requestDisplayInfo = requestDisplayInfoUiState,
isOnPasskeyIntroStateAlready = false,
- isPasskeyFirstUse
+ isPasskeyFirstUse = isPasskeyFirstUse,
)!!,
getCredentialUiState = null,
cancelRequestState = cancelUiRequestState
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 29ec970966d6..4d2bb4c6016a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -250,9 +250,15 @@ class CredentialSelectorViewModel(
return
}
val newUiState = CreateFlowUtils.toCreateCredentialUiState(
- prevUiState.enabledProviders, prevUiState.disabledProviders,
- userConfigRepo.getDefaultProviderId(), prevUiState.requestDisplayInfo, true,
- userConfigRepo.getIsPasskeyFirstUse())
+ enabledProviders = prevUiState.enabledProviders,
+ disabledProviders = prevUiState.disabledProviders,
+ defaultProviderIdPreferredByApp =
+ prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
+ defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
+ requestDisplayInfo = prevUiState.requestDisplayInfo,
+ isOnPasskeyIntroStateAlready = true,
+ isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+ )
if (newUiState == null) {
Log.d(Constants.LOG_TAG, "Unable to update create ui state")
onInternalError()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 64addf237f9b..e8d3b1f45be3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -464,6 +464,9 @@ class CreateFlowUtils {
createCredentialRequest.candidateQueryData,
createCredentialRequest.isSystemProviderRequired
)
+ val appPreferredDefaultProviderId: String? =
+ if (!requestInfo.hasPermissionToOverrideDefault()) null
+ else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider
return when (createCredentialRequestJetpack) {
is CreatePasswordRequest -> RequestDisplayInfo(
createCredentialRequestJetpack.id,
@@ -472,6 +475,7 @@ class CreateFlowUtils {
appLabel,
context.getDrawable(R.drawable.ic_password_24) ?: return null,
preferImmediatelyAvailableCredentials = false,
+ appPreferredDefaultProviderId = appPreferredDefaultProviderId,
)
is CreatePublicKeyCredentialRequest -> {
newRequestDisplayInfoFromPasskeyJson(
@@ -480,6 +484,7 @@ class CreateFlowUtils {
context = context,
preferImmediatelyAvailableCredentials =
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
+ appPreferredDefaultProviderId = appPreferredDefaultProviderId,
)
}
is CreateCustomCredentialRequest -> {
@@ -495,6 +500,7 @@ class CreateFlowUtils {
typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
preferImmediatelyAvailableCredentials = false,
+ appPreferredDefaultProviderId = appPreferredDefaultProviderId,
)
}
else -> null
@@ -504,20 +510,27 @@ class CreateFlowUtils {
fun toCreateCredentialUiState(
enabledProviders: List<EnabledProviderInfo>,
disabledProviders: List<DisabledProviderInfo>?,
- defaultProviderId: String?,
+ defaultProviderIdPreferredByApp: String?,
+ defaultProviderIdSetByUser: String?,
requestDisplayInfo: RequestDisplayInfo,
isOnPasskeyIntroStateAlready: Boolean,
isPasskeyFirstUse: Boolean,
): CreateCredentialUiState? {
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
- var defaultProvider: EnabledProviderInfo? = null
+ var defaultProviderPreferredByApp: EnabledProviderInfo? = null
+ var defaultProviderSetByUser: EnabledProviderInfo? = null
var createOptionsPairs:
MutableList<Pair<CreateOptionInfo, EnabledProviderInfo>> = mutableListOf()
enabledProviders.forEach { enabledProvider ->
- if (defaultProviderId != null) {
- if (enabledProvider.id == defaultProviderId) {
- defaultProvider = enabledProvider
+ if (defaultProviderIdPreferredByApp != null) {
+ if (enabledProvider.id == defaultProviderIdPreferredByApp) {
+ defaultProviderPreferredByApp = enabledProvider
+ }
+ }
+ if (defaultProviderIdSetByUser != null) {
+ if (enabledProvider.id == defaultProviderIdSetByUser) {
+ defaultProviderSetByUser = enabledProvider
}
}
if (enabledProvider.createOptions.isNotEmpty()) {
@@ -536,12 +549,14 @@ class CreateFlowUtils {
remoteEntry = currRemoteEntry
}
}
+ val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
val initialScreenState = toCreateScreenState(
- /*createOptionSize=*/createOptionsPairs.size,
- /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
- /*requestDisplayInfo=*/requestDisplayInfo,
- /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
- /*isPasskeyFirstUse=*/isPasskeyFirstUse
+ createOptionSize = createOptionsPairs.size,
+ isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
+ requestDisplayInfo = requestDisplayInfo,
+ defaultProvider = defaultProvider,
+ remoteEntry = remoteEntry,
+ isPasskeyFirstUse = isPasskeyFirstUse
) ?: return null
return CreateCredentialUiState(
enabledProviders = enabledProviders,
@@ -667,6 +682,7 @@ class CreateFlowUtils {
appLabel: String,
context: Context,
preferImmediatelyAvailableCredentials: Boolean,
+ appPreferredDefaultProviderId: String?,
): RequestDisplayInfo? {
val json = JSONObject(requestJson)
var passkeyUsername = ""
@@ -687,6 +703,7 @@ class CreateFlowUtils {
appLabel,
context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
preferImmediatelyAvailableCredentials,
+ appPreferredDefaultProviderId,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 14bf4f23384b..2df0c7a9b1e8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -27,8 +27,12 @@ import androidx.compose.ui.unit.dp
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@Composable
-fun CredentialListSectionHeader(text: String) {
- InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant)
+fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
+ InternalSectionHeader(
+ text = text,
+ color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ applyTopPadding = !isFirstSection
+ )
}
@Composable
@@ -37,8 +41,10 @@ fun MoreAboutPasskeySectionHeader(text: String) {
}
@Composable
-private fun InternalSectionHeader(text: String, color: Color) {
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 8.dp)) {
+private fun InternalSectionHeader(text: String, color: Color, applyTopPadding: Boolean = false) {
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(
+ top = if (applyTopPadding) 8.dp else 0.dp
+ )) {
SectionHeaderText(
text,
modifier = Modifier.padding(top = 20.dp, bottom = 8.dp),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 66d7db896247..96010cc66821 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -435,7 +435,7 @@ fun MoreOptionsRowIntroCard(
}
SheetContainerCard {
item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) }
- item { Divider(thickness = 24.dp, color = Color.Transparent) }
+ item { Divider(thickness = 16.dp, color = Color.Transparent) }
item {
HeadlineText(
text = stringResource(
@@ -633,6 +633,7 @@ fun MoreAboutPasskeysIntroCard(
}
}
item {
+ Divider(thickness = 8.dp, color = Color.Transparent)
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.public_key_cryptography_title)
)
@@ -641,6 +642,7 @@ fun MoreAboutPasskeysIntroCard(
}
}
item {
+ Divider(thickness = 8.dp, color = Color.Transparent)
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.improved_account_security_title)
)
@@ -649,6 +651,7 @@ fun MoreAboutPasskeysIntroCard(
}
}
item {
+ Divider(thickness = 8.dp, color = Color.Transparent)
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.seamless_transition_title)
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 4332fb34ce79..12bb6298b282 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -107,6 +107,7 @@ data class RequestDisplayInfo(
val appName: String,
val typeIcon: Drawable,
val preferImmediatelyAvailableCredentials: Boolean,
+ val appPreferredDefaultProviderId: String?,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index b8b7ac33bd9f..98bd020b234b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -323,12 +323,15 @@ fun AllSignInOptionCard(
bottomPadding = 0.dp,
)
}) {
+ var isFirstSection = true
// For username
items(sortedUserNameToCredentialEntryList) { item ->
PerUserNameCredentials(
perUserNameCredentialEntryList = item,
onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
// Locked password manager
if (authenticationEntryList.isNotEmpty()) {
@@ -336,7 +339,9 @@ fun AllSignInOptionCard(
LockedCredentials(
authenticationEntryList = authenticationEntryList,
onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
}
// From another device
@@ -346,15 +351,19 @@ fun AllSignInOptionCard(
RemoteEntryCard(
remoteEntry = remoteEntry,
onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
}
// Manage sign-ins (action chips)
item {
ActionChips(
providerInfoList = providerInfoList,
- onEntrySelected = onEntrySelected
+ onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
}
onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD)
@@ -367,6 +376,7 @@ fun AllSignInOptionCard(
fun ActionChips(
providerInfoList: List<ProviderInfo>,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
val actionChips = providerInfoList.flatMap { it.actionEntryList }
if (actionChips.isEmpty()) {
@@ -374,7 +384,8 @@ fun ActionChips(
}
CredentialListSectionHeader(
- text = stringResource(R.string.get_dialog_heading_manage_sign_ins)
+ text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
@@ -389,9 +400,11 @@ fun ActionChips(
fun RemoteEntryCard(
remoteEntry: RemoteEntryInfo,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
CredentialListSectionHeader(
- text = stringResource(R.string.get_dialog_heading_from_another_device)
+ text = stringResource(R.string.get_dialog_heading_from_another_device),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(
@@ -413,9 +426,11 @@ fun RemoteEntryCard(
fun LockedCredentials(
authenticationEntryList: List<AuthenticationEntryInfo>,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
CredentialListSectionHeader(
- text = stringResource(R.string.get_dialog_heading_locked_password_managers)
+ text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(
@@ -433,11 +448,13 @@ fun LockedCredentials(
fun PerUserNameCredentials(
perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
CredentialListSectionHeader(
text = stringResource(
R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
- )
+ ),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index b8e196fb8787..d8e1eb0f0860 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,10 +19,12 @@ package com.android.keyguard;
import static java.util.Collections.emptySet;
import android.content.Context;
+import android.os.Build;
import android.os.Trace;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
import android.widget.GridLayout;
import com.android.systemui.R;
@@ -118,6 +120,16 @@ public class KeyguardStatusView extends GridLayout {
}
@Override
+ public ViewPropertyAnimator animate() {
+ if (Build.IS_DEBUGGABLE) {
+ throw new IllegalArgumentException(
+ "KeyguardStatusView does not support ViewPropertyAnimator. "
+ + "Use PropertyAnimator instead.");
+ }
+ return super.animate();
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Trace.beginSection("KeyguardStatusView#onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index a678edc0eb06..651c9796140e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -18,8 +18,8 @@ package com.android.keyguard;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import android.util.Property;
import android.view.View;
-import android.view.ViewPropertyAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.log.LogBuffer;
@@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.google.errorprone.annotations.CompileTimeConstant;
+import java.util.function.Consumer;
+
/**
* Helper class for updating visibility of keyguard views based on keyguard and status bar state.
* This logic is shared by both the keyguard status view and the keyguard user switcher.
@@ -83,47 +85,49 @@ public class KeyguardVisibilityHelper {
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
- mView.animate().cancel();
+ PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
boolean isOccluded = mKeyguardStateController.isOccluded();
mKeyguardViewVisibilityAnimating = false;
if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
&& statusBarState != KEYGUARD) || goingToFullShade) {
mKeyguardViewVisibilityAnimating = true;
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setDuration(160)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(
- mAnimateKeyguardStatusViewGoneEndRunnable);
+
+ AnimationProperties animProps = new AnimationProperties()
+ .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_OUT)
+ .setAnimationEndAction(mSetGoneEndAction);
if (keyguardFadingAway) {
- mView.animate()
- .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
- .start();
+ animProps
+ .setDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+ .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration());
log("goingToFullShade && keyguardFadingAway");
} else {
+ animProps.setDelay(0).setDuration(160);
log("goingToFullShade && !keyguardFadingAway");
}
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
} else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
mView.setVisibility(View.VISIBLE);
mKeyguardViewVisibilityAnimating = true;
mView.setAlpha(0f);
- mView.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setDuration(320)
- .setInterpolator(Interpolators.ALPHA_IN)
- .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 1f,
+ new AnimationProperties()
+ .setDelay(0)
+ .setDuration(320)
+ .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_IN)
+ .setAnimationEndAction(
+ property -> mSetVisibleEndRunnable.run()),
+ true /* animate */);
log("keyguardFadingAway transition w/ Y Aniamtion");
} else if (statusBarState == KEYGUARD) {
if (keyguardFadingAway) {
mKeyguardViewVisibilityAnimating = true;
- ViewPropertyAnimator animator = mView.animate()
- .alpha(0)
- .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
- .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
+ AnimationProperties animProps = new AnimationProperties()
+ .setDelay(0)
+ .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_LINEAR_IN)
+ .setAnimationEndAction(mSetInvisibleEndAction);
if (mAnimateYPos) {
float target = mView.getY() - mView.getHeight() * 0.05f;
int delay = 0;
@@ -135,13 +139,16 @@ public class KeyguardVisibilityHelper {
PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target,
mAnimationProperties,
true /* animate */);
- animator.setDuration(duration)
- .setStartDelay(delay);
+ animProps.setDuration(duration)
+ .setDelay(delay);
log("keyguardFadingAway transition w/ Y Aniamtion");
} else {
log("keyguardFadingAway transition w/o Y Animation");
}
- animator.start();
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 0f,
+ animProps,
+ true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
log("ScreenOff transition");
mKeyguardViewVisibilityAnimating = true;
@@ -149,7 +156,7 @@ public class KeyguardVisibilityHelper {
// Ask the screen off animation controller to animate the keyguard visibility for us
// since it may need to be cancelled due to keyguard lifecycle events.
mScreenOffAnimationController.animateInKeyguard(
- mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
+ mView, mSetVisibleEndRunnable);
} else {
log("Direct set Visibility to VISIBLE");
mView.setVisibility(View.VISIBLE);
@@ -163,19 +170,25 @@ public class KeyguardVisibilityHelper {
mLastOccludedState = isOccluded;
}
- private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
- mKeyguardViewVisibilityAnimating = false;
- mView.setVisibility(View.INVISIBLE);
- log("Callback Set Visibility to INVISIBLE");
+ private final Consumer<Property> mSetInvisibleEndAction = new Consumer<>() {
+ @Override
+ public void accept(Property property) {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.INVISIBLE);
+ log("Callback Set Visibility to INVISIBLE");
+ }
};
- private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
- mKeyguardViewVisibilityAnimating = false;
- mView.setVisibility(View.GONE);
- log("CallbackSet Visibility to GONE");
+ private final Consumer<Property> mSetGoneEndAction = new Consumer<>() {
+ @Override
+ public void accept(Property property) {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.GONE);
+ log("CallbackSet Visibility to GONE");
+ }
};
- private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+ private final Runnable mSetVisibleEndRunnable = () -> {
mKeyguardViewVisibilityAnimating = false;
mView.setVisibility(View.VISIBLE);
log("Callback Set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2987004347e4..f28aeadc7acb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -600,9 +600,6 @@ object Flags {
// TODO(b/254512507): Tracking Bug
val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
- // TODO(b/266983432) Tracking Bug
- val SHARESHEET_CUSTOM_ACTIONS = releasedFlag(1501, "sharesheet_custom_actions")
-
// TODO(b/266982749) Tracking Bug
val SHARESHEET_RESELECTION_ACTION = releasedFlag(1502, "sharesheet_reselection_action")
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aedd9762a601..222a0f4fa248 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3333,7 +3333,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mGestureRecorder = recorder;
mHideExpandedRunnable = hideExpandedRunnable;
- mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ mNotificationStackScrollLayoutController.setShelfController(
+ notificationShelfController);
+ }
mNotificationShelfController = notificationShelfController;
mLockscreenShadeTransitionController.bindController(notificationShelfController);
updateMaxDisplayedNotifications(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d1c6aef7b306..7eb63da38028 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -53,6 +53,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -99,6 +101,9 @@ public class NotificationShelf extends ActivatableNotificationView implements St
private boolean mSensitiveRevealAnimEndabled;
private boolean mShelfRefactorFlagEnabled;
private boolean mCanModifyColorOfNotifications;
+ private boolean mCanInteract;
+ private NotificationStackScrollLayout mHostLayout;
+ private NotificationRoundnessManager mRoundnessManager;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -135,6 +140,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController hostLayoutController) {
+ assertRefactorFlagDisabled();
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -142,6 +148,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
});
}
+ public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
+ NotificationRoundnessManager roundnessManager) {
+ if (!checkRefactorFlagEnabled()) return;
+ mAmbientState = ambientState;
+ mHostLayout = hostLayout;
+ mRoundnessManager = roundnessManager;
+ }
+
private void updateResources() {
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
@@ -233,7 +247,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
}
- viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
+ viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
viewState.hideSensitive = false;
viewState.setXTranslation(getTranslationX());
viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -276,6 +290,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
}
+ private int getSpeedBumpIndex() {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getSpeedBumpIndex();
+ } else {
+ return mHostLayoutController.getSpeedBumpIndex();
+ }
+ }
+
/**
* @param fractionToShade Fraction of lockscreen to shade transition
* @param shortestWidth Shortest width to use for lockscreen shelf
@@ -388,8 +410,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St
int baseZHeight = mAmbientState.getBaseZHeight();
int clipTopAmount = 0;
- for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
- ExpandableView child = mHostLayoutController.getChildAt(i);
+ for (int i = 0; i < getHostLayoutChildCount(); i++) {
+ ExpandableView child = getHostLayoutChildAt(i);
if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
continue;
}
@@ -474,11 +496,11 @@ public class NotificationShelf extends ActivatableNotificationView implements St
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
- mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
+ mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates();
- for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
- View child = mHostLayoutController.getChildAt(i);
+ for (int i = 0; i < getHostLayoutChildCount(); i++) {
+ View child = getHostLayoutChildAt(i);
if (!(child instanceof ExpandableNotificationRow)
|| child.getVisibility() == GONE) {
continue;
@@ -493,6 +515,22 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
}
+ private ExpandableView getHostLayoutChildAt(int index) {
+ if (mShelfRefactorFlagEnabled) {
+ return (ExpandableView) mHostLayout.getChildAt(index);
+ } else {
+ return mHostLayoutController.getChildAt(index);
+ }
+ }
+
+ private int getHostLayoutChildCount() {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getChildCount();
+ } else {
+ return mHostLayoutController.getChildCount();
+ }
+ }
+
private boolean canModifyColorOfNotifications() {
if (mShelfRefactorFlagEnabled) {
return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
@@ -515,7 +553,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
&& anv == mAmbientState.getTrackedHeadsUpRow();
final boolean shouldUpdateCornerRoundness = viewStart < shelfStart
- && !mHostLayoutController.isViewAffectedBySwipe(anv)
+ && !isViewAffectedBySwipe(anv)
&& !isUnlockedHeadsUp
&& !isHunGoingToShade
&& !anv.isAboveShelf()
@@ -567,6 +605,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
}
+ private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
+ if (!mShelfRefactorFlagEnabled) {
+ return mHostLayoutController.isViewAffectedBySwipe(expandableView);
+ } else {
+ return mRoundnessManager.isViewAffectedBySwipe(expandableView);
+ }
+ }
+
/**
* Clips transient views to the top of the shelf - Transient views are only used for
* disappearing views/animations and need to be clipped correctly by the shelf to ensure they
@@ -574,8 +620,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St
* swipes quickly.
*/
private void clipTransientViews() {
- for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
- View transientView = mHostLayoutController.getTransientView(i);
+ for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
+ View transientView = getHostLayoutTransientView(i);
if (transientView instanceof ExpandableView) {
ExpandableView transientExpandableView = (ExpandableView) transientView;
updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -583,6 +629,22 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
}
+ private View getHostLayoutTransientView(int index) {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getTransientView(index);
+ } else {
+ return mHostLayoutController.getTransientView(index);
+ }
+ }
+
+ private int getHostLayoutTransientViewCount() {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getTransientViewCount();
+ } else {
+ return mHostLayoutController.getTransientViewCount();
+ }
+ }
+
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
if (getClipTopAmount() != 0) {
@@ -922,18 +984,27 @@ public class NotificationShelf extends ActivatableNotificationView implements St
@Override
public void onStateChanged(int newState) {
+ assertRefactorFlagDisabled();
mStatusBarState = newState;
updateInteractiveness();
}
private void updateInteractiveness() {
- mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+ mInteractive = canInteract() && mHasItemsInStableShelf;
setClickable(mInteractive);
setFocusable(mInteractive);
setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
+ private boolean canInteract() {
+ if (mShelfRefactorFlagEnabled) {
+ return mCanInteract;
+ } else {
+ return mStatusBarState == StatusBarState.KEYGUARD;
+ }
+ }
+
@Override
protected boolean isInteractive() {
return mInteractive;
@@ -972,8 +1043,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
private void assertRefactorFlagDisabled() {
if (mShelfRefactorFlagEnabled) {
- throw new IllegalStateException(
- "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
+ NotificationShelfController.throwIllegalFlagStateError(false);
}
}
@@ -995,8 +1065,22 @@ public class NotificationShelf extends ActivatableNotificationView implements St
mCanModifyColorOfNotifications = canModifyColorOfNotifications;
}
+ public void setCanInteract(boolean canInteract) {
+ if (!checkRefactorFlagEnabled()) return;
+ mCanInteract = canInteract;
+ updateInteractiveness();
+ }
+
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
- mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+ mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf);
+ }
+
+ private int getIndexOfViewInHostLayout(ExpandableView child) {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.indexOfChild(child);
+ } else {
+ return mHostLayoutController.indexOfChild(child);
+ }
}
/**
@@ -1011,6 +1095,11 @@ public class NotificationShelf extends ActivatableNotificationView implements St
mShelfRefactorFlagEnabled = enabled;
}
+ public void requestRoundnessResetFor(ExpandableView child) {
+ if (!checkRefactorFlagEnabled()) return;
+ child.requestRoundnessReset(SHELF_SCROLL);
+ }
+
/**
* This method resets the OnScroll roundness of a view to 0f
* <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
index bf3d47c4a9ca..07cfd0d8019c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar
+import android.util.Log
import android.view.View
import android.view.View.OnClickListener
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -51,4 +54,29 @@ interface NotificationShelfController {
/** @see View.setOnClickListener */
fun setOnClickListener(listener: OnClickListener)
+
+ companion object {
+ @JvmStatic
+ fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ throwIllegalFlagStateError(expected = false)
+ }
+ }
+
+ @JvmStatic
+ fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
+ featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
+ if (!enabled) {
+ Log.wtf("NotifShelf", getErrorMessage(expected = true))
+ }
+ }
+
+ @JvmStatic
+ fun throwIllegalFlagStateError(expected: Boolean): Nothing =
+ error(getErrorMessage(expected))
+
+ private fun getErrorMessage(expected: Boolean): String =
+ "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
+ if (expected) "disabled" else "enabled"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index ecd0c41ff82d..fcff4376811e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -48,6 +48,10 @@ public abstract class AnimatableProperty {
View.SCALE_Y, R.id.scale_y_animator_tag, R.id.scale_y_animator_start_value_tag,
R.id.scale_y_animator_end_value_tag);
+ public static final AnimatableProperty ALPHA = AnimatableProperty.from(
+ View.ALPHA, R.id.alpha_animator_tag, R.id.alpha_animator_start_value_tag,
+ R.id.alpha_animator_end_value_tag);
+
/**
* Similar to X, however this doesn't allow for any other modifications other than from this
* property. When using X, it's possible that the view is laid out during the animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 98b4cc4d7d27..950ab5d2f5ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -299,6 +299,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
private boolean mShowGroupBackgroundWhenExpanded;
+ /**
+ * True if we always show the collapsed layout on lockscreen because vertical space is low.
+ */
+ private boolean mSaveSpaceOnLockscreen;
+
+ /**
+ * True if we use intrinsic height regardless of vertical space available on lockscreen.
+ */
+ private boolean mIgnoreLockscreenConstraints;
+
private OnClickListener mExpandClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
@@ -394,6 +404,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mGroupExpansionChanging;
}
+ public void setSaveSpaceOnLockscreen(boolean saveSpace) {
+ mSaveSpaceOnLockscreen = saveSpace;
+ }
+
+ public boolean getSaveSpaceOnLockscreen() {
+ return mSaveSpaceOnLockscreen;
+ }
+
public void setGroupExpansionChanging(boolean changing) {
mGroupExpansionChanging = changing;
}
@@ -2547,11 +2565,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
@Override
+ public int getHeightWithoutLockscreenConstraints() {
+ mIgnoreLockscreenConstraints = true;
+ final int height = getIntrinsicHeight();
+ mIgnoreLockscreenConstraints = false;
+ return height;
+ }
+
+ @Override
public int getIntrinsicHeight() {
if (isUserLocked()) {
return getActualHeight();
- }
- if (mGuts != null && mGuts.isExposed()) {
+ } else if (mGuts != null && mGuts.isExposed()) {
return mGuts.getIntrinsicHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
return mPrivateLayout.getMinHeight();
@@ -2573,13 +2598,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return getCollapsedHeight();
}
}
-
/**
* @return {@code true} if the notification can show it's heads up layout. This is mostly true
* except for legacy use cases.
*/
public boolean canShowHeadsUp() {
- if (mOnKeyguard && !isDozing() && !isBypassEnabled() && !mEntry.isStickyAndNotDemoted()) {
+ if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
+ (!mEntry.isStickyAndNotDemoted()
+ || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
return false;
}
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 9df6ba9910cc..5edff5f4e5d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -291,6 +291,11 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
long duration) {
}
+ public int getHeightWithoutLockscreenConstraints() {
+ // ExpandableNotificationRow overrides this.
+ return getHeight();
+ }
+
/**
* @return The desired notification height.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
index db550c00b4a1..8ba65f7a1418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -32,6 +32,10 @@ constructor(
private val keyguardRepository: KeyguardRepository,
private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
) {
+ /** Is the shelf showing on the keyguard? */
+ val isShowingOnKeyguard: Flow<Boolean>
+ get() = keyguardRepository.isKeyguardShowing
+
/** Is the system in a state where the shelf is just a static display of notification icons? */
val isShelfStatic: Flow<Boolean>
get() =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index bd531caccc5d..b190cf658fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
import android.view.View
-import android.view.View.OnAttachStateChangeListener
import android.view.accessibility.AccessibilityManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -29,7 +28,6 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.NotificationShelfController
-import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
@@ -40,9 +38,11 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.NotificationTapHelper
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import com.android.systemui.util.kotlin.getValue
+import dagger.Lazy
+import javax.inject.Inject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
/**
* Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
@@ -61,11 +61,13 @@ constructor(
private val a11yManager: AccessibilityManager,
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
- private val statusBarStateController: SysuiStatusBarStateController,
+ hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
) : NotificationShelfController {
+ private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
+
override val view: NotificationShelf
- get() = shelf
+ get() = unsupported
init {
shelf.apply {
@@ -87,22 +89,9 @@ constructor(
falsingCollector,
)
.init()
- val onAttachStateListener =
- object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View) {
- statusBarStateController.addCallback(
- shelf,
- SysuiStatusBarStateController.RANK_SHELF,
- )
- }
-
- override fun onViewDetachedFromWindow(v: View) {
- statusBarStateController.removeCallback(shelf)
- }
- }
- shelf.addOnAttachStateChangeListener(onAttachStateListener)
- if (shelf.isAttachedToWindow) {
- onAttachStateListener.onViewAttachedToWindow(shelf)
+ hostController.setShelf(shelf)
+ hostController.setOnNotificationRemovedListener { child, _ ->
+ view.requestRoundnessResetFor(child)
}
}
@@ -121,16 +110,14 @@ constructor(
override fun bind(
ambientState: AmbientState,
notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
- ) {
- shelf.bind(ambientState, notificationStackScrollLayoutController)
- }
+ ) = unsupported
override fun setOnClickListener(listener: View.OnClickListener) {
shelf.setOnClickListener(listener)
}
private val unsupported: Nothing
- get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
+ get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
}
/** Binds a [NotificationShelf] to its backend. */
@@ -141,6 +128,7 @@ object NotificationShelfViewBinder {
viewModel.canModifyColorOfNotifications
.onEach(shelf::setCanModifyColorOfNotifications)
.launchIn(this)
+ viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index b84834adf122..5e297c89dd2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -30,6 +30,10 @@ class NotificationShelfViewModel
constructor(
private val interactor: NotificationShelfInteractor,
) {
+ /** Is the shelf allowed to be clickable when it has content? */
+ val isClickable: Flow<Boolean>
+ get() = interactor.isShowingOnKeyguard
+
/** Is the shelf allowed to modify the color of notifications in the host layout? */
val canModifyColorOfNotifications: Flow<Boolean>
get() = interactor.isShelfStatic.map { static -> !static }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
index 112d48c115c2..00b9aa42ab26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
@@ -32,6 +32,7 @@ public class AnimationProperties {
public long duration;
public long delay;
private ArrayMap<Property, Interpolator> mInterpolatorMap;
+ private Consumer<Property> mAnimationCancelAction;
private Consumer<Property> mAnimationEndAction;
/**
@@ -50,27 +51,43 @@ public class AnimationProperties {
* @return a listener that will be added for a given property during its animation.
*/
public AnimatorListenerAdapter getAnimationFinishListener(Property property) {
- if (mAnimationEndAction == null) {
+ if (mAnimationEndAction == null && mAnimationCancelAction == null) {
return null;
}
+ Consumer<Property> cancelAction = mAnimationCancelAction;
Consumer<Property> endAction = mAnimationEndAction;
return new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationCancel(Animator animation) {
- mCancelled = true;
+ mCancelled = true;
+ if (cancelAction != null) {
+ cancelAction.accept(property);
+ }
}
@Override
public void onAnimationEnd(Animator animation) {
- if (!mCancelled) {
+ if (!mCancelled && endAction != null) {
endAction.accept(property);
}
}
};
}
+ /**
+ * Add a callback for animation cancellation.
+ */
+ public AnimationProperties setAnimationCancelAction(Consumer<Property> listener) {
+ mAnimationCancelAction = listener;
+ return this;
+ }
+
+ /**
+ * Add a callback for animation ending successfully. The callback will not be called when the
+ * animations is cancelled.
+ */
public AnimationProperties setAnimationEndAction(Consumer<Property> listener) {
mAnimationEndAction = listener;
return this;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e47e4146d4c0..af608a7f3a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5137,8 +5137,26 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
requestChildrenUpdate();
}
+ public void setShelf(NotificationShelf shelf) {
+ if (!NotificationShelfController.checkRefactorFlagEnabled(
+ mAmbientState.getFeatureFlags())) {
+ return;
+ }
+ int index = -1;
+ if (mShelf != null) {
+ index = indexOfChild(mShelf);
+ removeView(mShelf);
+ }
+ mShelf = shelf;
+ addView(mShelf, index);
+ mAmbientState.setShelf(mShelf);
+ mStateAnimator.setShelf(mShelf);
+ shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
+ }
+
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setShelfController(NotificationShelfController notificationShelfController) {
+ NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 792746c60134..1c8727f1b092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -73,6 +73,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
@@ -1382,6 +1383,7 @@ public class NotificationStackScrollLayoutController {
}
public void setShelfController(NotificationShelfController notificationShelfController) {
+ NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
mView.setShelfController(notificationShelfController);
}
@@ -1593,6 +1595,11 @@ public class NotificationStackScrollLayoutController {
mView.setOnNotificationRemovedListener(listener);
}
+ public void setShelf(NotificationShelf shelf) {
+ if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
+ mView.setShelf(shelf);
+ }
+
/**
* Enum for UiEvent logged from this class
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 092242814869..c7cb70c1da05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -23,12 +23,14 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.Compile
+import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade
import com.android.systemui.util.children
import java.io.PrintWriter
import javax.inject.Inject
@@ -51,11 +53,10 @@ class NotificationStackSizeCalculator
constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
+ private val mediaDataManager: MediaDataManager,
@Main private val resources: Resources
) {
- private lateinit var lastComputeHeightLog : String
-
/**
* Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
* If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -67,67 +68,157 @@ constructor(
/** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
private var dividerHeight by notNull<Float>()
+ /**
+ * True when there is not enough vertical space to show at least one notification with heads up
+ * layout. When true, notifications always show collapsed layout.
+ */
+ private var saveSpaceOnLockscreen = false
+
init {
updateResources()
}
/**
* Returns whether notifications and (shelf if visible) can fit in total space available.
- * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon.
+ * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon.
*/
private fun canStackFitInSpace(
stackHeight: StackHeight,
- spaceForNotifications: Float,
- spaceForShelf: Float,
- ): Boolean {
-
- val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight
- var canFit: Boolean
+ notifSpace: Float,
+ shelfSpace: Float,
+ ): FitResult {
+ val (notifHeight, notifHeightSaveSpace, shelfHeightWithSpaceBefore) = stackHeight
if (shelfHeightWithSpaceBefore == 0f) {
- canFit = notificationsHeight <= spaceForNotifications
+ if (notifHeight <= notifSpace) {
+ log {
+ "\tcanStackFitInSpace[FIT] = notifHeight[$notifHeight]" +
+ " <= notifSpace[$notifSpace]"
+ }
+ return FitResult.FIT
+ }
+ if (notifHeightSaveSpace <= notifSpace) {
+ log {
+ "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+ " = notifHeightSaveSpace[$notifHeightSaveSpace]" +
+ " <= notifSpace[$notifSpace]"
+ }
+ return FitResult.FIT_IF_SAVE_SPACE
+ }
log {
- "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" +
- " <= spaceForNotifications[$spaceForNotifications]"
+ "\tcanStackFitInSpace[NO_FIT]" +
+ " = notifHeightSaveSpace[$notifHeightSaveSpace] > notifSpace[$notifSpace]"
}
+ return FitResult.NO_FIT
} else {
- canFit =
- (notificationsHeight + shelfHeightWithSpaceBefore) <=
- (spaceForNotifications + spaceForShelf)
- log {
- "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" +
- " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
- " <= (spaceForNotifications[$spaceForNotifications] " +
- " + spaceForShelf[$spaceForShelf])"
+ if ((notifHeight + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)) {
+ log {
+ "\tcanStackFitInSpace[FIT] = (notifHeight[$notifHeight]" +
+ " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+ " <= (notifSpace[$notifSpace] " +
+ " + spaceForShelf[$shelfSpace])"
+ }
+ return FitResult.FIT
+ } else if (
+ (notifHeightSaveSpace + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)
+ ) {
+ log {
+ "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+ " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+ " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+ " <= (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+ }
+ return FitResult.FIT_IF_SAVE_SPACE
+ } else {
+ log {
+ "\tcanStackFitInSpace[NO_FIT]" +
+ " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+ " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+ " > (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+ }
+ return FitResult.NO_FIT
}
}
- return canFit
}
/**
- * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many
- * notifications to show. This number is only valid in keyguard.
+ * Given the [notifSpace] and [shelfSpace] constraints, calculate how many notifications to
+ * show. This number is only valid in keyguard.
*
* @param totalAvailableSpace space for notifications. This includes the space for the shelf.
*/
fun computeMaxKeyguardNotifications(
stack: NotificationStackScrollLayout,
- spaceForNotifications: Float,
- spaceForShelf: Float,
- shelfIntrinsicHeight: Float
+ notifSpace: Float,
+ shelfSpace: Float,
+ shelfHeight: Float,
): Int {
+ log { "\n " }
+ log {
+ "computeMaxKeyguardNotifications ---" +
+ "\n\tnotifSpace $notifSpace" +
+ "\n\tspaceForShelf $shelfSpace" +
+ "\n\tshelfIntrinsicHeight $shelfHeight"
+ }
+ if (notifSpace + shelfSpace <= 0f) {
+ log { "--- No space to show anything. maxNotifs=0" }
+ return 0
+ }
log { "\n" }
- val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
- /* computeHeight= */ false)
+ val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+ val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation()
- var maxNotifications =
+ log { "\tGet maxNotifWithoutSavingSpace ---" }
+ val maxNotifWithoutSavingSpace =
stackHeightSequence.lastIndexWhile { heightResult ->
canStackFitInSpace(
heightResult,
- spaceForNotifications = spaceForNotifications,
- spaceForShelf = spaceForShelf)
+ notifSpace = notifSpace,
+ shelfSpace = shelfSpace
+ ) == FitResult.FIT
+ }
+
+ // How many notifications we can show at heightWithoutLockscreenConstraints
+ var minCountAtHeightWithoutConstraints =
+ if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1
+ log {
+ "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " +
+ "isMediaShowing=$isMediaShowing" +
+ "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints"
+ }
+ log { "\n" }
+
+ var maxNotifications: Int
+ if (maxNotifWithoutSavingSpace >= minCountAtHeightWithoutConstraints) {
+ saveSpaceOnLockscreen = false
+ maxNotifications = maxNotifWithoutSavingSpace
+ log {
+ "\tDo NOT save space. maxNotifications=maxNotifWithoutSavingSpace=$maxNotifications"
+ }
+ } else {
+ log { "\tSAVE space ---" }
+ saveSpaceOnLockscreen = true
+ maxNotifications =
+ stackHeightSequence.lastIndexWhile { heightResult ->
+ canStackFitInSpace(
+ heightResult,
+ notifSpace = notifSpace,
+ shelfSpace = shelfSpace
+ ) != FitResult.NO_FIT
+ }
+ log { "\t--- maxNotifications=$maxNotifications" }
+ }
+
+ // Must update views immediately to avoid mismatches between initial HUN layout height
+ // and the height adapted to lockscreen space constraints, which causes jump cuts.
+ stack.showableChildren().toList().forEach { currentNotification ->
+ run {
+ if (currentNotification is ExpandableNotificationRow) {
+ currentNotification.saveSpaceOnLockscreen = saveSpaceOnLockscreen
+ }
}
+ }
if (onLockscreen()) {
maxNotifications = min(maxKeyguardNotifications, maxNotifications)
@@ -137,53 +228,80 @@ constructor(
maxNotifications = max(0, maxNotifications)
log {
val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else ""
- "computeMaxKeyguardNotifications(" +
- " spaceForNotifications=$spaceForNotifications" +
- " spaceForShelf=$spaceForShelf" +
- " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence"
+ "--- computeMaxKeyguardNotifications(" +
+ " notifSpace=$notifSpace" +
+ " shelfSpace=$shelfSpace" +
+ " shelfHeight=$shelfHeight) -> $maxNotifications$sequence"
}
+ log { "\n" }
return maxNotifications
}
/**
- * Given the [maxNotifications] constraint, calculates the height of the
+ * Given the [maxNotifs] constraint, calculates the height of the
* [NotificationStackScrollLayout]. This might or might not be in keyguard.
*
* @param stack stack containing notifications as children.
- * @param maxNotifications Maximum number of notifications. When reached, the others will go
- * into the shelf.
- * @param shelfIntrinsicHeight height of the shelf, without any padding. It might be zero.
- *
+ * @param maxNotifs Maximum number of notifications. When reached, the others will go into the
+ * shelf.
+ * @param shelfHeight height of the shelf, without any padding. It might be zero.
* @return height of the stack, including shelf height, if needed.
*/
fun computeHeight(
stack: NotificationStackScrollLayout,
- maxNotifications: Int,
- shelfIntrinsicHeight: Float
+ maxNotifs: Int,
+ shelfHeight: Float
): Float {
log { "\n" }
- lastComputeHeightLog = ""
- val heightPerMaxNotifications =
- computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
- /* computeHeight= */ true)
-
- val (notificationsHeight, shelfHeightWithSpaceBefore) =
- heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
- heightPerMaxNotifications.last() // Height with all notifications visible.
+ log { "computeHeight ---" }
+
+ val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+
+ val (notifsHeight, notifsHeightSavingSpace, shelfHeightWithSpaceBefore) =
+ stackHeightSequence.elementAtOrElse(maxNotifs) {
+ stackHeightSequence.last() // Height with all notifications visible.
+ }
+
+ var height: Float
+ if (saveSpaceOnLockscreen) {
+ height = notifsHeightSavingSpace + shelfHeightWithSpaceBefore
+ log {
+ "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+ " -> $height=($notifsHeightSavingSpace+$shelfHeightWithSpaceBefore)," +
+ " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+ }
+ } else {
+ height = notifsHeight + shelfHeightWithSpaceBefore
+ log {
+ "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+ " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" +
+ " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
}
- lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
- "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
- "${notificationsHeight + shelfHeightWithSpaceBefore}" +
- " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
- log {
- lastComputeHeightLog
}
- return notificationsHeight + shelfHeightWithSpaceBefore
+ return height
}
+ private enum class FitResult {
+ FIT,
+ FIT_IF_SAVE_SPACE,
+ NO_FIT
+ }
+
+ data class SpaceNeeded(
+ // Float height of spaceNeeded when showing heads up layout for FSI HUNs.
+ val whenEnoughSpace: Float,
+
+ // Float height of space needed when showing collapsed layout for FSI HUNs.
+ val whenSavingSpace: Float
+ )
+
private data class StackHeight(
// Float height with ith max notifications (not including shelf)
- val notificationsHeight: Float,
+ val notifsHeight: Float,
+
+ // Float height with ith max notifications
+ // (not including shelf, using collapsed layout for FSI HUN)
+ val notifsHeightSavingSpace: Float,
// Float height of shelf (0 if shelf is not showing), and space before the shelf that
// changes during the lockscreen <=> full shade transition.
@@ -193,20 +311,27 @@ constructor(
private fun computeHeightPerNotificationLimit(
stack: NotificationStackScrollLayout,
shelfHeight: Float,
- computeHeight: Boolean
): Sequence<StackHeight> = sequence {
- log { "computeHeightPerNotificationLimit" }
-
val children = stack.showableChildren().toList()
var notifications = 0f
+ var notifsWithCollapsedHun = 0f
var previous: ExpandableView? = null
val onLockscreen = onLockscreen()
// Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
- yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight))
+ yield(
+ StackHeight(
+ notifsHeight = 0f,
+ notifsHeightSavingSpace = 0f,
+ shelfHeightWithSpaceBefore = shelfHeight
+ )
+ )
children.forEachIndexed { i, currentNotification ->
- notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+ val space = getSpaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+ notifications += space.whenEnoughSpace
+ notifsWithCollapsedHun += space.whenSavingSpace
+
previous = currentNotification
val shelfWithSpaceBefore =
@@ -219,22 +344,23 @@ constructor(
stack,
previous = currentNotification,
current = children[firstViewInShelfIndex],
- currentIndex = firstViewInShelfIndex)
+ currentIndex = firstViewInShelfIndex
+ )
spaceBeforeShelf + shelfHeight
}
- val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
- "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
- if (computeHeight) {
- lastComputeHeightLog += "\n" + currentLog
- }
log {
- currentLog
+ "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
+ "notifsHeightSavingSpace=$notifsWithCollapsedHun" +
+ " shelfWithSpaceBefore=$shelfWithSpaceBefore"
}
yield(
StackHeight(
- notificationsHeight = notifications,
- shelfHeightWithSpaceBefore = shelfWithSpaceBefore))
+ notifsHeight = notifications,
+ notifsHeightSavingSpace = notifsWithCollapsedHun,
+ shelfHeightWithSpaceBefore = shelfWithSpaceBefore
+ )
+ )
}
}
@@ -256,32 +382,46 @@ constructor(
}
@VisibleForTesting
- fun spaceNeeded(
+ fun getSpaceNeeded(
view: ExpandableView,
visibleIndex: Int,
previousView: ExpandableView?,
stack: NotificationStackScrollLayout,
- onLockscreen: Boolean
- ): Float {
+ onLockscreen: Boolean,
+ ): SpaceNeeded {
assert(view.isShowable(onLockscreen))
+ // Must use heightWithoutLockscreenConstraints because intrinsicHeight references
+ // mSaveSpaceOnLockscreen and using intrinsicHeight here will result in stack overflow.
+ val height = view.heightWithoutLockscreenConstraints.toFloat()
+ val gapAndDividerHeight =
+ calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
+
var size =
if (onLockscreen) {
if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) {
- view.intrinsicHeight.toFloat()
+ height
} else {
view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
}
} else {
- view.intrinsicHeight.toFloat()
+ height
+ }
+ size += gapAndDividerHeight
+
+ var sizeWhenSavingSpace =
+ if (onLockscreen) {
+ view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
+ } else {
+ height
}
+ sizeWhenSavingSpace += gapAndDividerHeight
- size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
- return size
+ return SpaceNeeded(size, sizeWhenSavingSpace)
}
fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+ pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
}
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 118bfc55dd4c..0cd3401ae541 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -175,15 +175,19 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// We animate the Y properly separately using the PropertyAnimator, as the panel
// view also needs to update the end position.
PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
- PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
+ PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
AnimationProperties().setDuration(duration.toLong()),
true /* animate */)
- keyguardView.animate()
+ // Cancel any existing CUJs before starting the animation
+ interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+
+ PropertyAnimator.setProperty(
+ keyguardView, AnimatableProperty.ALPHA, 1f,
+ AnimationProperties()
+ .setDelay(0)
.setDuration(duration.toLong())
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .alpha(1f)
- .withEndAction {
+ .setAnimationEndAction {
aodUiAnimationPlaying = false
// Lock the keyguard if it was waiting for the screen off animation to end.
@@ -199,30 +203,23 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// Done going to sleep, reset this flag.
decidedToAnimateGoingToSleep = null
- // We need to unset the listener. These are persistent for future animators
- keyguardView.animate().setListener(null)
interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
}
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationCancel(animation: Animator?) {
- // If we're cancelled, reset state flags/listeners. The end action above
- // will not be called, which is what we want since that will finish the
- // screen off animation and show the lockscreen, which we don't want if we
- // were cancelled.
- aodUiAnimationPlaying = false
- decidedToAnimateGoingToSleep = null
- keyguardView.animate().setListener(null)
-
- interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
- }
-
- override fun onAnimationStart(animation: Animator?) {
- interactionJankMonitor.begin(
- mCentralSurfaces.notificationShadeWindowView,
- CUJ_SCREEN_OFF_SHOW_AOD)
- }
- })
- .start()
+ .setAnimationCancelAction {
+ // If we're cancelled, reset state flags/listeners. The end action above
+ // will not be called, which is what we want since that will finish the
+ // screen off animation and show the lockscreen, which we don't want if we
+ // were cancelled.
+ aodUiAnimationPlaying = false
+ decidedToAnimateGoingToSleep = null
+ interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+ }
+ .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
+ true /* animate */)
+ interactionJankMonitor.begin(
+ mCentralSurfaces.notificationShadeWindowView,
+ CUJ_SCREEN_OFF_SHOW_AOD
+ )
}
override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e9f0dcb4eb51..928e0115287d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -61,6 +61,7 @@ import javax.inject.Inject;
* Manages the user switcher on the Keyguard.
*/
@KeyguardUserSwitcherScope
+@Deprecated
public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
private static final String TAG = "KeyguardUserSwitcherController";
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
new file mode 100644
index 000000000000..c587f2edd601
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import dagger.Lazy
+import kotlin.reflect.KProperty
+
+/**
+ * Extension operator that allows developers to use [dagger.Lazy] as a property delegate:
+ * ```kotlin
+ * class MyClass @Inject constructor(
+ * lazyDependency: dagger.Lazy<Foo>,
+ * ) {
+ * val dependency: Foo by lazyDependency
+ * }
+ * ```
+ */
+operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
index 14e5f9e63ebe..2cc375b3d0ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -24,7 +24,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -68,4 +67,22 @@ class NotificationShelfInteractorTest : SysuiTestCase() {
assertThat(shelfStatic).isTrue()
}
+
+ @Test
+ fun shelfOnKeyguard_whenKeyguardShowing() = runTest {
+ val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+ keyguardRepository.setKeyguardShowing(true)
+
+ assertThat(onKeyguard).isTrue()
+ }
+
+ @Test
+ fun shelfNotOnKeyguard_whenKeyguardNotShowing() = runTest {
+ val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(onKeyguard).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index 6c5fb8bcff22..439edaf3faaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -69,4 +69,22 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
assertThat(canModifyNotifColor).isFalse()
}
+
+ @Test
+ fun isClickable_whenKeyguardShowing() = runTest {
+ val isClickable by collectLastValue(underTest.isClickable)
+
+ keyguardRepository.setKeyguardShowing(true)
+
+ assertThat(isClickable).isTrue()
+ }
+
+ @Test
+ fun isNotClickable_whenKeyguardNotShowing() = runTest {
+ val isClickable by collectLastValue(underTest.isClickable)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(isClickable).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index e6f10cdafe03..5279740a8702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -23,6 +23,7 @@ import android.view.View.VISIBLE
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -48,6 +49,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+ @Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var stackLayout: NotificationStackScrollLayout
private val testableResources = mContext.orCreateTestableResources
@@ -67,7 +69,9 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
NotificationStackSizeCalculator(
statusBarStateController = sysuiStatusBarStateController,
lockscreenShadeTransitionController = lockscreenShadeTransitionController,
- testableResources.resources)
+ mediaDataManager = mediaDataManager,
+ testableResources.resources
+ )
}
@Test
@@ -76,7 +80,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
val maxNotifications =
computeMaxKeyguardNotifications(
- rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f)
+ rows,
+ spaceForNotifications = 0f,
+ spaceForShelf = 0f,
+ shelfHeight = 0f
+ )
assertThat(maxNotifications).isEqualTo(0)
}
@@ -91,7 +99,8 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
rows,
spaceForNotifications = Float.MAX_VALUE,
spaceForShelf = Float.MAX_VALUE,
- shelfHeight)
+ shelfHeight
+ )
assertThat(maxNotifications).isEqualTo(numberOfRows)
}
@@ -111,6 +120,28 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
+ fun computeMaxKeyguardNotifications_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f)
+
+ val row = createMockRow(10f, isSticky = true)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val maxNotifications =
+ computeMaxKeyguardNotifications(
+ listOf(row),
+ /* spaceForNotifications= */ 5f,
+ /* spaceForShelf= */ 0f,
+ /* shelfHeight= */ 0f
+ )
+
+ assertThat(maxNotifications).isEqualTo(1)
+ }
+
+ @Test
fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
setGapHeight(gapHeight)
val shelfHeight = shelfHeight + dividerHeight
@@ -126,7 +157,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
val maxNotifications =
computeMaxKeyguardNotifications(
- rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+ rows,
+ spaceForNotifications + 1,
+ spaceForShelf,
+ shelfHeight
+ )
assertThat(maxNotifications).isEqualTo(2)
}
@@ -136,24 +171,23 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
// Each row in separate section.
setGapHeight(gapHeight)
- val spaceForNotifications =
+ val notifSpace =
listOf(
rowHeight,
dividerHeight + gapHeight + rowHeight,
)
.sum()
- val spaceForShelf = dividerHeight + gapHeight + shelfHeight
- val spaceUsed = spaceForNotifications + spaceForShelf
+ val shelfSpace = dividerHeight + gapHeight + shelfHeight
+ val spaceUsed = notifSpace + shelfSpace
val rows =
listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
val maxNotifications =
- computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight)
+ computeMaxKeyguardNotifications(rows, notifSpace, shelfSpace, shelfHeight)
assertThat(maxNotifications).isEqualTo(2)
- val height =
- sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+ val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
assertThat(height).isEqualTo(spaceUsed)
}
@@ -170,11 +204,14 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
// test that we only use space required
val maxNotifications =
computeMaxKeyguardNotifications(
- rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+ rows,
+ spaceForNotifications + 1,
+ spaceForShelf,
+ shelfHeight
+ )
assertThat(maxNotifications).isEqualTo(1)
- val height =
- sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+ val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
assertThat(height).isEqualTo(spaceUsed)
}
@@ -200,60 +237,101 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
- fun spaceNeeded_onLockscreen_usesMinHeight() {
+ fun getSpaceNeeded_onLockscreenEnoughSpaceStickyHun_intrinsicHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
- val expandableView = createMockRow(rowHeight)
+ val row = createMockRow(10f, isSticky = true)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val space =
+ sizeCalculator.getSpaceNeeded(
+ row,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true
+ )
+ assertThat(space.whenEnoughSpace).isEqualTo(10f)
+ }
+
+ @Test
+ fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ val row = createMockRow(rowHeight)
+ whenever(row.heightWithoutLockscreenConstraints).thenReturn(10)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val space =
+ sizeCalculator.getSpaceNeeded(
+ row,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true
+ )
+ assertThat(space.whenEnoughSpace).isEqualTo(5)
+ }
+
+ @Test
+ fun getSpaceNeeded_onLockscreenSavingSpaceStickyHun_minHeight() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ val expandableView = createMockRow(10f, isSticky = true)
whenever(expandableView.getMinHeight(any())).thenReturn(5)
- whenever(expandableView.intrinsicHeight).thenReturn(10)
val space =
- sizeCalculator.spaceNeeded(
+ sizeCalculator.getSpaceNeeded(
expandableView,
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true)
- assertThat(space).isEqualTo(5)
+ onLockscreen = true
+ )
+ assertThat(space.whenSavingSpace).isEqualTo(5)
}
@Test
- fun spaceNeeded_fsiHunOnLockscreen_usesIntrinsicHeight() {
+ fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
- val expandableView = createMockStickyRow(rowHeight)
+ val expandableView = createMockRow(rowHeight)
whenever(expandableView.getMinHeight(any())).thenReturn(5)
whenever(expandableView.intrinsicHeight).thenReturn(10)
val space =
- sizeCalculator.spaceNeeded(
- expandableView,
- visibleIndex = 0,
- previousView = null,
- stack = stackLayout,
- onLockscreen = true)
- assertThat(space).isEqualTo(10)
+ sizeCalculator.getSpaceNeeded(
+ expandableView,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true
+ )
+ assertThat(space.whenSavingSpace).isEqualTo(5)
}
@Test
- fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() {
+ fun getSpaceNeeded_notOnLockscreen_intrinsicHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
val expandableView = createMockRow(rowHeight)
- whenever(expandableView.getMinHeight(any())).thenReturn(5)
- whenever(expandableView.intrinsicHeight).thenReturn(10)
+ whenever(expandableView.getMinHeight(any())).thenReturn(1)
val space =
- sizeCalculator.spaceNeeded(
+ sizeCalculator.getSpaceNeeded(
expandableView,
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = false)
- assertThat(space).isEqualTo(10)
+ onLockscreen = false
+ )
+ assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
+ assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
}
private fun computeMaxKeyguardNotifications(
@@ -264,7 +342,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
): Int {
setupChildren(rows)
return sizeCalculator.computeMaxKeyguardNotifications(
- stackLayout, spaceForNotifications, spaceForShelf, shelfHeight)
+ stackLayout,
+ spaceForNotifications,
+ spaceForShelf,
+ shelfHeight
+ )
}
private fun setupChildren(children: List<ExpandableView>) {
@@ -280,30 +362,13 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
private fun createMockRow(
height: Float = rowHeight,
+ isSticky: Boolean = false,
isRemoved: Boolean = false,
- visibility: Int = VISIBLE
- ): ExpandableNotificationRow {
- val row = mock(ExpandableNotificationRow::class.java)
- val entry = mock(NotificationEntry::class.java)
- val sbn = mock(StatusBarNotification::class.java)
- whenever(entry.sbn).thenReturn(sbn)
- whenever(row.entry).thenReturn(entry)
- whenever(row.isRemoved).thenReturn(isRemoved)
- whenever(row.visibility).thenReturn(visibility)
- whenever(row.getMinHeight(any())).thenReturn(height.toInt())
- whenever(row.intrinsicHeight).thenReturn(height.toInt())
- return row
- }
-
- private fun createMockStickyRow(
- height: Float = rowHeight,
- isRemoved: Boolean = false,
- visibility: Int = VISIBLE
+ visibility: Int = VISIBLE,
): ExpandableNotificationRow {
val row = mock(ExpandableNotificationRow::class.java)
val entry = mock(NotificationEntry::class.java)
- whenever(entry.isStickyAndNotDemoted).thenReturn(true)
-
+ whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky)
val sbn = mock(StatusBarNotification::class.java)
whenever(entry.sbn).thenReturn(sbn)
whenever(row.entry).thenReturn(entry)
@@ -311,6 +376,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
whenever(row.visibility).thenReturn(visibility)
whenever(row.getMinHeight(any())).thenReturn(height.toInt())
whenever(row.intrinsicHeight).thenReturn(height.toInt())
+ whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt())
return row
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 746c92e485b7..02c459b11d07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,12 +16,10 @@
package com.android.systemui.statusbar.phone
-import android.animation.Animator
import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
@@ -39,10 +37,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@@ -111,27 +107,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
controller.onStartedWakingUp()
}
- @Test
- fun testAnimClearsEndListener() {
- val keyguardView = View(context)
- val animator = spy(keyguardView.animate())
- val keyguardSpy = spy(keyguardView)
- Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
- val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
- val endAction = ArgumentCaptor.forClass(Runnable::class.java)
- controller.animateInKeyguard(keyguardSpy, Runnable {})
- Mockito.verify(animator).setListener(listener.capture())
- Mockito.verify(animator).withEndAction(endAction.capture())
-
- // Verify that the listener is cleared if we cancel it.
- listener.value.onAnimationCancel(null)
- Mockito.verify(animator).setListener(null)
-
- // Verify that the listener is also cleared if the end action is triggered.
- endAction.value.run()
- verify(animator, times(2)).setListener(null)
- }
-
/**
* The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
* animation to start. If the device is woken up during the screen off, we should *never* do
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 46d019b241b7..6e2e5f75b8f3 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -107,7 +107,7 @@ public class RescueParty {
static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
"namespace_to_package_mapping";
@VisibleForTesting
- static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+ static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
private static final String NAME = "rescue-party-observer";
@@ -117,6 +117,8 @@ public class RescueParty {
"persist.device_config.configuration.disable_rescue_party";
private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
"persist.device_config.configuration.disable_rescue_party_factory_reset";
+ private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
+ "persist.device_config.configuration.rescue_party_throttle_duration_min";
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -722,7 +724,9 @@ public class RescueParty {
private boolean shouldThrottleReboot() {
Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
long now = System.currentTimeMillis();
- return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+ long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+ return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
}
private boolean isPersistentSystemApp(@NonNull String packageName) {
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 9e61ce405ca7..0767218ec065 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
+import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -187,6 +188,12 @@ class BroadcastProcessQueue {
private @Reason int mRunnableAtReason = REASON_EMPTY;
private boolean mRunnableAtInvalidated;
+ /**
+ * Last state applied by {@link #updateDeferredStates}, used to quickly
+ * determine if a state transition is occurring.
+ */
+ private boolean mLastDeferredStates;
+
private boolean mUidCached;
private boolean mProcessInstrumented;
private boolean mProcessPersistent;
@@ -235,7 +242,15 @@ class BroadcastProcessQueue {
*/
@Nullable
public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
- int recordIndex, boolean wouldBeSkipped) {
+ int recordIndex, boolean wouldBeSkipped,
+ @NonNull BroadcastConsumer deferredStatesApplyConsumer) {
+ // When updateDeferredStates() has already applied a deferred state to
+ // all pending items, apply to this new broadcast too
+ if (mLastDeferredStates && record.deferUntilActive
+ && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
+ deferredStatesApplyConsumer.accept(record, recordIndex);
+ }
+
if (record.isReplacePending()) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex,
wouldBeSkipped);
@@ -340,7 +355,12 @@ class BroadcastProcessQueue {
* Predicates that choose to remove a broadcast <em>must</em> finish
* delivery of the matched broadcast, to ensure that situations like ordered
* broadcasts are handled consistently.
+ *
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
*/
+ @CheckResult
public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
@NonNull BroadcastConsumer consumer, boolean andRemove) {
boolean didSomething = false;
@@ -353,6 +373,7 @@ class BroadcastProcessQueue {
return didSomething;
}
+ @CheckResult
private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
boolean andRemove) {
@@ -369,6 +390,10 @@ class BroadcastProcessQueue {
args.recycle();
it.remove();
onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped);
+ } else {
+ // Even if we're leaving broadcast in queue, it may have
+ // been mutated in such a way to change our runnable time
+ invalidateRunnableAt();
}
didSomething = true;
}
@@ -380,32 +405,44 @@ class BroadcastProcessQueue {
/**
* Update the actively running "warm" process for this process.
+ *
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
*/
- public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
+ @CheckResult
+ public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
this.app = app;
- if (app != null) {
- setUidCached(uidCached);
- setProcessInstrumented(app.getActiveInstrumentation() != null);
- setProcessPersistent(app.isPersistent());
- } else {
- setUidCached(uidCached);
- setProcessInstrumented(false);
- setProcessPersistent(false);
- }
// Since we may have just changed our PID, invalidate cached strings
mCachedToString = null;
mCachedToShortString = null;
+
+ boolean didSomething = false;
+ if (app != null) {
+ didSomething |= setUidCached(uidCached);
+ didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
+ didSomething |= setProcessPersistent(app.isPersistent());
+ } else {
+ didSomething |= setUidCached(uidCached);
+ didSomething |= setProcessInstrumented(false);
+ didSomething |= setProcessPersistent(false);
+ }
+ return didSomething;
}
/**
* Update if this process is in the "cached" state, typically signaling that
* broadcast dispatch should be paused or delayed.
*/
- private void setUidCached(boolean uidCached) {
+ @CheckResult
+ private boolean setUidCached(boolean uidCached) {
if (mUidCached != uidCached) {
mUidCached = uidCached;
invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
}
}
@@ -414,10 +451,14 @@ class BroadcastProcessQueue {
* signaling that broadcast dispatch should bypass all pauses or delays, to
* avoid holding up test suites.
*/
- private void setProcessInstrumented(boolean instrumented) {
+ @CheckResult
+ private boolean setProcessInstrumented(boolean instrumented) {
if (mProcessInstrumented != instrumented) {
mProcessInstrumented = instrumented;
invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
}
}
@@ -425,10 +466,14 @@ class BroadcastProcessQueue {
* Update if this process is in the "persistent" state, which signals broadcast dispatch should
* bypass all pauses or delays to prevent the system from becoming out of sync with itself.
*/
- private void setProcessPersistent(boolean persistent) {
+ @CheckResult
+ private boolean setProcessPersistent(boolean persistent) {
if (mProcessPersistent != persistent) {
mProcessPersistent = persistent;
invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
}
}
@@ -648,8 +693,20 @@ class BroadcastProcessQueue {
return mActive != null;
}
- void forceDelayBroadcastDelivery(long delayedDurationMs) {
- mForcedDelayedDurationMs = delayedDurationMs;
+ /**
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
+ */
+ @CheckResult
+ boolean forceDelayBroadcastDelivery(long delayedDurationMs) {
+ if (mForcedDelayedDurationMs != delayedDurationMs) {
+ mForcedDelayedDurationMs = delayedDurationMs;
+ invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
+ }
}
/**
@@ -721,10 +778,21 @@ class BroadcastProcessQueue {
* broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
* waiting. This is typically used in case there are callers waiting for "barrier" to be
* reached.
+ *
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
*/
+ @CheckResult
@VisibleForTesting
- void setPrioritizeEarliest(boolean prioritizeEarliest) {
- mPrioritizeEarliest = prioritizeEarliest;
+ boolean setPrioritizeEarliest(boolean prioritizeEarliest) {
+ if (mPrioritizeEarliest != prioritizeEarliest) {
+ mPrioritizeEarliest = prioritizeEarliest;
+ invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
+ }
}
/**
@@ -912,9 +980,9 @@ class BroadcastProcessQueue {
}
/**
- * Update {@link #getRunnableAt()} if it's currently invalidated.
+ * Update {@link #getRunnableAt()}, when needed.
*/
- private void updateRunnableAt() {
+ void updateRunnableAt() {
if (!mRunnableAtInvalidated) return;
mRunnableAtInvalidated = false;
@@ -1027,10 +1095,44 @@ class BroadcastProcessQueue {
}
/**
+ * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our
+ * pending broadcasts, when needed.
+ */
+ void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer,
+ @NonNull BroadcastConsumer clearConsumer) {
+ // When all we have pending is deferred broadcasts, and we're cached,
+ // then we want everything to be marked deferred
+ final boolean wantDeferredStates = (mCountDeferred > 0)
+ && (mCountDeferred == mCountEnqueued) && mUidCached;
+
+ if (mLastDeferredStates != wantDeferredStates) {
+ mLastDeferredStates = wantDeferredStates;
+ if (wantDeferredStates) {
+ forEachMatchingBroadcast((r, i) -> {
+ return r.deferUntilActive
+ && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
+ }, applyConsumer, false);
+ } else {
+ forEachMatchingBroadcast((r, i) -> {
+ return r.deferUntilActive
+ && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
+ }, clearConsumer, false);
+ }
+ }
+ }
+
+ /**
* Check overall health, confirming things are in a reasonable state and
* that we're not wedged.
*/
public void assertHealthLocked() {
+ // If we're not actively running, we should be sorted into the runnable
+ // list, and if we're invalidated then someone likely forgot to invoke
+ // updateRunnableList() to re-sort us into place
+ if (!isActive()) {
+ checkState(!mRunnableAtInvalidated, "mRunnableAtInvalidated");
+ }
+
assertHealthLocked(mPending);
assertHealthLocked(mPendingUrgent);
assertHealthLocked(mPendingOffload);
@@ -1133,19 +1235,30 @@ class BroadcastProcessQueue {
return mCachedToShortString;
}
+ public String describeStateLocked() {
+ return describeStateLocked(SystemClock.uptimeMillis());
+ }
+
+ public String describeStateLocked(@UptimeMillisLong long now) {
+ final StringBuilder sb = new StringBuilder();
+ if (isRunnable()) {
+ sb.append("runnable at ");
+ TimeUtils.formatDuration(getRunnableAt(), now, sb);
+ } else {
+ sb.append("not runnable");
+ }
+ sb.append(" because ");
+ sb.append(reasonToString(mRunnableAtReason));
+ return sb.toString();
+ }
+
@NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
if ((mActive == null) && isEmpty()) return;
pw.print(toShortString());
- if (isRunnable()) {
- pw.print(" runnable at ");
- TimeUtils.formatDuration(getRunnableAt(), now, pw);
- } else {
- pw.print(" not runnable");
- }
- pw.print(" because ");
- pw.print(reasonToString(mRunnableAtReason));
+ pw.print(" ");
+ pw.print(describeStateLocked(now));
pw.println();
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index e532c15addd0..8735f8a37b8b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -327,6 +327,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return;
}
+ // To place ourselves correctly in the runnable list, we may need to
+ // update internals that may have been invalidated; we wait until now at
+ // the last possible moment to avoid duplicated work
+ queue.updateDeferredStates(mBroadcastConsumerDeferApply, mBroadcastConsumerDeferClear);
+ queue.updateRunnableAt();
+
final boolean wantQueue = queue.isRunnable();
final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
|| (queue.runnableAtNext != null);
@@ -352,8 +358,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If app isn't running, and there's nothing in the queue, clean up
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
- } else {
- updateQueueDeferred(queue);
}
}
@@ -619,14 +623,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
enqueuedBroadcast = true;
final BroadcastRecord replacedBroadcast = queue.enqueueOrReplaceBroadcast(
- r, i, wouldBeSkipped);
+ r, i, wouldBeSkipped, mBroadcastConsumerDeferApply);
if (replacedBroadcast != null) {
replacedBroadcasts.add(replacedBroadcast);
}
- if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
- setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
- "deferred at enqueue time");
- }
updateRunnableList(queue);
enqueueUpdateRunningList();
}
@@ -1249,14 +1249,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.resultExtras = null;
};
- private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> {
+ private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
- "mBroadcastConsumerDefer");
+ "mBroadcastConsumerDeferApply");
};
- private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> {
+ private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
- "mBroadcastConsumerUndoDefer");
+ "mBroadcastConsumerDeferClear");
};
/**
@@ -1272,7 +1272,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final long now = SystemClock.uptimeMillis();
if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
mLastTestFailureTime = now;
- pw.println("Test " + label + " failed due to " + leaf.toShortString());
+ pw.println("Test " + label + " failed due to " + leaf.toShortString() + " "
+ + leaf.describeStateLocked());
pw.flush();
}
return false;
@@ -1309,34 +1310,25 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return didSomething;
}
- private void forEachMatchingQueue(
+ private boolean forEachMatchingQueue(
@NonNull Predicate<BroadcastProcessQueue> queuePredicate,
@NonNull Consumer<BroadcastProcessQueue> queueConsumer) {
+ boolean didSomething = false;
for (int i = mProcessQueues.size() - 1; i >= 0; i--) {
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
if (queuePredicate.test(leaf)) {
queueConsumer.accept(leaf);
updateRunnableList(leaf);
+ didSomething = true;
}
leaf = leaf.processNameNext;
}
}
- }
-
- private void updateQueueDeferred(
- @NonNull BroadcastProcessQueue leaf) {
- if (leaf.isDeferredUntilActive()) {
- leaf.forEachMatchingBroadcast((r, i) -> {
- return r.deferUntilActive && (r.getDeliveryState(i)
- == BroadcastRecord.DELIVERY_PENDING);
- }, mBroadcastConsumerDefer, false);
- } else if (leaf.hasDeferredBroadcasts()) {
- leaf.forEachMatchingBroadcast((r, i) -> {
- return r.deferUntilActive && (r.getDeliveryState(i)
- == BroadcastRecord.DELIVERY_DEFERRED);
- }, mBroadcastConsumerUndoDefer, false);
+ if (didSomething) {
+ enqueueUpdateRunningList();
}
+ return didSomething;
}
@Override
@@ -1359,8 +1351,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// Update internal state by refreshing values previously
// read from any known running process
setQueueProcess(leaf, leaf.app);
- updateQueueDeferred(leaf);
- updateRunnableList(leaf);
leaf = leaf.processNameNext;
}
enqueueUpdateRunningList();
@@ -1529,19 +1519,31 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
+ @SuppressWarnings("CheckResult")
private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
if (!queue.isProcessWarm()) {
- setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid));
+ // This is a bit awkward; we're in the middle of traversing the
+ // runnable queue, so we can't reorder that list if the runnable
+ // time changes here. However, if this process was just found to be
+ // warm via this operation, we're going to immediately promote it to
+ // be running, and any side effect of this operation will then apply
+ // after it's finished and is returned to the runnable list.
+ queue.setProcessAndUidCached(
+ mService.getProcessRecordLocked(queue.processName, queue.uid),
+ mUidCached.get(queue.uid, false));
}
}
/**
* Update the {@link ProcessRecord} associated with the given
- * {@link BroadcastProcessQueue}.
+ * {@link BroadcastProcessQueue}. Also updates any runnable status that
+ * might have changed as a side-effect.
*/
private void setQueueProcess(@NonNull BroadcastProcessQueue queue,
@Nullable ProcessRecord app) {
- queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false));
+ if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) {
+ updateRunnableList(queue);
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7aae4d5b0215..ffb40ee959a4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -579,7 +579,9 @@ class ProcessRecord implements WindowProcessListener {
processName = _processName;
sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage;
if (isSdkSandbox) {
- sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid;
+ final ApplicationInfo clientInfo = getClientInfoForSdkSandbox();
+ sdkSandboxClientAppVolumeUuid = clientInfo != null
+ ? clientInfo.volumeUuid : null;
} else {
sdkSandboxClientAppVolumeUuid = null;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 7ae31b2a114d..50d375c56f4a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -212,6 +212,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
// 1) Authenticated == true
// 2) Error occurred
// 3) Authenticated == false
+ // 4) onLockout
+ // 5) onLockoutTimed
mCallback.onClientFinished(this, true /* success */);
}
@@ -304,11 +306,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
PerformanceTracker.getInstanceForSensorId(getSensorId())
.incrementTimedLockoutForUser(getTargetUserId());
- try {
- getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ onError(error, 0 /* vendorCode */);
}
@Override
@@ -323,10 +321,6 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
PerformanceTracker.getInstanceForSensorId(getSensorId())
.incrementPermanentLockoutForUser(getTargetUserId());
- try {
- getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ onError(error, 0 /* vendorCode */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 1a53fec82d98..c5037b7012f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -465,7 +465,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
BaseClientMonitor clientMonitor,
boolean success) {
mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
- sensorId, requestId, success);
+ sensorId, requestId, client.wasAuthSuccessful());
}
});
});
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index cc2c9adfcba4..3492b2660c4a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED;
import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED;
import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -3655,6 +3656,26 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
for (String permission : pkg.getRequestedPermissions()) {
Integer permissionState = permissionStates.get(permission);
+
+ if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT)
+ && permissionState == null) {
+ final PackageStateInternal ps;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ final String[] useFullScreenIntentPackageNames =
+ mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_useFullScreenIntentPackages);
+ final boolean canUseFullScreenIntent = (ps != null && ps.isSystem())
+ || ArrayUtils.contains(useFullScreenIntentPackageNames,
+ pkg.getPackageName());
+ permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED
+ : PERMISSION_STATE_DENIED;
+ }
+
if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) {
continue;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 62144401223b..7718dd88b772 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1145,8 +1145,7 @@ class ActivityClientController extends IActivityClientController.Stub {
if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Multiwindow Fullscreen Request: %s", transition);
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- () -> r.mTransitionController.moveToCollecting(transition),
+ r.mTransitionController.queueCollecting(transition,
() -> {
executeFullscreenRequestTransition(fullscreenRequest, callback, r,
transition, true /* queued */);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1bcc05e6f116..0b98495c8e99 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9820,8 +9820,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
scheduleStopForRestartProcess();
};
if (mWmService.mSyncEngine.hasActiveSync()) {
- mWmService.mSyncEngine.queueSyncSet(
- () -> mTransitionController.moveToCollecting(transition), executeRestart);
+ mTransitionController.queueCollecting(transition, executeRestart);
} else {
mTransitionController.moveToCollecting(transition);
executeRestart.run();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8123c07052b0..e780716dd06c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2874,8 +2874,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
getTransitionController(), mWindowManager.mSyncEngine);
if (mWindowManager.mSyncEngine.hasActiveSync()) {
- mWindowManager.mSyncEngine.queueSyncSet(
- () -> getTransitionController().moveToCollecting(transition),
+ getTransitionController().queueCollecting(transition,
() -> {
if (!task.getWindowConfiguration().canResizeTask()) {
Slog.w(TAG, "resizeTask not allowed on task=" + task);
@@ -3629,9 +3628,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Pip-Enter: %s", transition);
- mWindowManager.mSyncEngine.queueSyncSet(
- () -> getTransitionController().moveToCollecting(transition),
- enterPipRunnable);
+ getTransitionController().queueCollecting(transition, enterPipRunnable);
} else {
// Move to collecting immediately to "claim" the sync-engine for this
// transition.
@@ -3647,9 +3644,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Pip-Enter: %s", transition);
- mWindowManager.mSyncEngine.queueSyncSet(
- () -> getTransitionController().moveToCollecting(transition),
- enterPipRunnable);
+ getTransitionController().queueCollecting(transition, enterPipRunnable);
} else {
if (transition != null) {
getTransitionController().moveToCollecting(transition);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07daa4b22ac9..ad934541267e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2335,9 +2335,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
transition.playNow();
};
if (display.mTransitionController.isCollecting()) {
- mWmService.mSyncEngine.queueSyncSet(
- () -> display.mTransitionController.moveToCollecting(transition),
- sendSleepTransition);
+ display.mTransitionController.queueCollecting(transition, sendSleepTransition);
} else {
display.mTransitionController.moveToCollecting(transition);
sendSleepTransition.run();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 10055bdd572b..89f975387667 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5635,8 +5635,7 @@ class Task extends TaskFragment {
if (mWmService.mSyncEngine.hasActiveSync()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Move-to-back: %s", transition);
- mWmService.mSyncEngine.queueSyncSet(
- () -> mTransitionController.moveToCollecting(transition),
+ mTransitionController.queueCollecting(transition,
() -> {
// Need to check again since this happens later and the system might
// be in a different state.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3a909cedc5ab..652c2977f40e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -287,7 +287,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (restoreBelow != null) {
final Task transientRootTask = activity.getRootTask();
- // Collect all visible activities which can be occluded by the transient activity to
+ // Collect all visible tasks which can be occluded by the transient activity to
// make sure they are in the participants so their visibilities can be updated when
// finishing transition.
((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
@@ -297,11 +297,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mTransientHideTasks.add(t);
}
if (t.isLeafTask()) {
- t.forAllActivities(r -> {
- if (r.isVisibleRequested()) {
- collect(r);
- }
- });
+ collect(t);
}
}
return t == restoreBelow;
@@ -904,6 +900,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.mFinishingTransition = this;
if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+ // Record all the now-hiding activities so that they are committed after
+ // recalculating visibilities. We just use mParticipants because we can and it will
+ // ensure proper reporting of `isInFinishTransition`.
+ for (int i = 0; i < mTransientHideTasks.size(); ++i) {
+ mTransientHideTasks.get(i).forAllActivities(r -> {
+ // Only check leaf-tasks that were collected
+ if (!mParticipants.contains(r.getTask())) return;
+ // Only concern ourselves with anything that can become invisible
+ if (!r.isVisible()) return;
+ mParticipants.add(r);
+ });
+ }
// The transient hide tasks could be occluded now, e.g. returning to home. So trigger
// the update to make the activities in the tasks invisible-requested, then the next
// step can continue to commit the visibility.
@@ -953,7 +961,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
enterAutoPip = true;
}
}
- if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
+ final ChangeInfo changeInfo = mChanges.get(ar);
+ // Due to transient-hide, there may be some activities here which weren't in the
+ // transition.
+ if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) {
// Legacy dispatch relies on this (for now).
ar.mEnteringAnimation = visibleAtTransitionEnd;
} else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
@@ -1609,7 +1620,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" id=" + mSyncId);
sb.append(" type=" + transitTypeToString(mType));
- sb.append(" flags=" + mFlags);
+ sb.append(" flags=0x" + Integer.toHexString(mFlags));
sb.append('}');
return sb.toString();
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 742ab7cec9ff..4c1c2ff1ac10 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -884,6 +884,15 @@ class TransitionController {
proto.end(token);
}
+ void queueCollecting(Transition transit, Runnable onCollectStart) {
+ mAtm.mWindowManager.mSyncEngine.queueSyncSet(
+ // Make sure to collect immediately to prevent another transition
+ // from sneaking in before it. Note: moveToCollecting internally
+ // calls startSyncSet.
+ () -> moveToCollecting(transit),
+ onCollectStart);
+ }
+
/**
* This manages the animating state of processes that are running remote animations for
* {@link #mTransitionPlayerProc}.
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 32d54d774b40..f63470f2bea4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -291,23 +291,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (type < 0) {
throw new IllegalArgumentException("Can't create transition with no type");
}
+ transition = new Transition(type, 0 /* flags */, mTransitionController,
+ mService.mWindowManager.mSyncEngine);
// If there is already a collecting transition, queue up a new transition and
// return that. The actual start and apply will then be deferred until that
// transition starts collecting. This should almost never happen except during
// tests.
if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
Slog.w(TAG, "startTransition() while one is already collecting.");
- final Transition nextTransition = new Transition(type, 0 /* flags */,
- mTransitionController, mService.mWindowManager.mSyncEngine);
+ final Transition nextTransition = transition;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Transition: %s", nextTransition);
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- // Make sure to collect immediately to prevent another transition
- // from sneaking in before it. Note: moveToCollecting internally
- // calls startSyncSet.
- () -> mTransitionController.moveToCollecting(nextTransition),
+ mTransitionController.queueCollecting(nextTransition,
() -> {
nextTransition.start();
+ nextTransition.mLogger.mStartWCT = wct;
applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
if (needsSetReady) {
nextTransition.setAllReady();
@@ -315,7 +313,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
});
return nextTransition.getToken();
}
- transition = mTransitionController.createTransition(type);
+ mTransitionController.moveToCollecting(transition);
}
if (!transition.isCollecting() && !transition.isForcePlaying()) {
Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
@@ -474,11 +472,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mTransitionController, mService.mWindowManager.mSyncEngine);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Transition for TaskFragment: %s", nextTransition);
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- // Make sure to collect immediately to prevent another transition
- // from sneaking in before it. Note: moveToCollecting internally
- // calls startSyncSet.
- () -> mTransitionController.moveToCollecting(nextTransition),
+ mTransitionController.queueCollecting(nextTransition,
() -> {
if (mTaskFragmentOrganizerController.isValidTransaction(wct)
&& (applyTransaction(wct, -1 /* syncId */, nextTransition, caller)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 93fbb845f534..29f9a306880f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19,6 +19,7 @@ package com.android.server.devicepolicy;
import static android.Manifest.permission.BIND_DEVICE_ADMIN;
import static android.Manifest.permission.LOCK_DEVICE;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
@@ -59,6 +60,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTI
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT;
@@ -7766,12 +7768,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Explicit behaviour
if (factoryReset) {
// TODO(b/254031494) Replace with new factory reset permission checks
- boolean hasPermission = isDeviceOwnerUserId(userId)
- || (isOrganizationOwnedDeviceWithManagedProfile()
- && calledOnParentInstance);
- Preconditions.checkState(hasPermission,
- "Admin %s does not have permission to factory reset the device.",
- userId);
+ if (!isPermissionCheckFlagEnabled()) {
+ boolean hasPermission = isDeviceOwnerUserId(userId)
+ || (isOrganizationOwnedDeviceWithManagedProfile()
+ && calledOnParentInstance);
+ Preconditions.checkCallAuthorization(hasPermission,
+ "Admin %s does not have permission to factory reset the device.",
+ userId);
+ }
wipeDevice = true;
} else {
Preconditions.checkCallAuthorization(!isSystemUser,
@@ -13467,164 +13471,162 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
// Map of user restriction to permission.
- private static final HashMap<String, String> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
+ private static final HashMap<String, String[]> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
{
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.ENSURE_VERIFY_APPS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+ UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_WIFI_TETHERING, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_WIFI_DIRECT, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_USER_SWITCH, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER);
+ UserManager.DISALLOW_ADJUST_VOLUME, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNMUTE_MICROPHONE, MANAGE_DEVICE_POLICY_MICROPHONE);
+ UserManager.DISALLOW_AIRPLANE_MODE, new String[]{MANAGE_DEVICE_POLICY_AIRPLANE_MODE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNMUTE_DEVICE, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+ UserManager.DISALLOW_AMBIENT_DISPLAY, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNINSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+ UserManager.DISALLOW_APPS_CONTROL, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNIFIED_PASSWORD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+ UserManager.DISALLOW_AUTOFILL, new String[]{MANAGE_DEVICE_POLICY_AUTOFILL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS);
+ UserManager.DISALLOW_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SMS, MANAGE_DEVICE_POLICY_SMS);
+ UserManager.DISALLOW_BLUETOOTH_SHARING, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_CAMERA, new String[]{MANAGE_DEVICE_POLICY_CAMERA});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SHARE_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+ UserManager.DISALLOW_CAMERA_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_CAMERA_TOGGLE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+ UserManager.DISALLOW_CELLULAR_2G, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SET_WALLPAPER, MANAGE_DEVICE_POLICY_WALLPAPER);
+ UserManager.DISALLOW_CHANGE_WIFI_STATE, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SET_USER_ICON, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_CONFIG_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SAFE_BOOT, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+ UserManager.DISALLOW_CONFIG_BRIGHTNESS, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_RUN_IN_BACKGROUND, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_REMOVE_USER, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_CONFIG_CREDENTIALS, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_PRINTING, MANAGE_DEVICE_POLICY_PRINTING);
+ UserManager.DISALLOW_CONFIG_DATE_TIME, new String[]{MANAGE_DEVICE_POLICY_TIME});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_OUTGOING_CALLS, MANAGE_DEVICE_POLICY_CALLS);
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS, new String[]{MANAGE_DEFAULT_APPLICATIONS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_OUTGOING_BEAM, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+ UserManager.DISALLOW_CONFIG_LOCALE, new String[]{MANAGE_DEVICE_POLICY_LOCALE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_NETWORK_RESET, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_CONFIG_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA);
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_MODIFY_ACCOUNTS, MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT);
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS, new String[]{MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_MICROPHONE);
+ UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
- MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+ UserManager.DISALLOW_CONFIG_TETHERING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+ UserManager.DISALLOW_CONFIG_VPN, new String[]{MANAGE_DEVICE_POLICY_VPN});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_INSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+ UserManager.DISALLOW_CONFIG_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_FUN, MANAGE_DEVICE_POLICY_FUN);
+ UserManager.DISALLOW_CONTENT_CAPTURE, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_FACTORY_RESET, MANAGE_DEVICE_POLICY_FACTORY_RESET);
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES);
+ UserManager.DISALLOW_CREATE_WINDOWS, new String[]{MANAGE_DEVICE_POLICY_WINDOWS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_DATA_ROAMING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+ UserManager.DISALLOW_DATA_ROAMING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CREATE_WINDOWS, MANAGE_DEVICE_POLICY_WINDOWS);
+ UserManager.DISALLOW_DEBUGGING_FEATURES, new String[]{MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONTENT_SUGGESTIONS, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+ UserManager.DISALLOW_FACTORY_RESET, new String[]{MANAGE_DEVICE_POLICY_FACTORY_RESET});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONTENT_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+ UserManager.DISALLOW_FUN, new String[]{MANAGE_DEVICE_POLICY_FUN});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_INSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_VPN, MANAGE_DEVICE_POLICY_VPN);
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_TETHERING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, MANAGE_DEVICE_POLICY_DISPLAY);
+ UserManager.DISALLOW_MICROPHONE_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_PRIVATE_DNS, MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS);
+ UserManager.DISALLOW_MODIFY_ACCOUNTS, new String[]{MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, new String[]{MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+ UserManager.DISALLOW_NETWORK_RESET, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_LOCALE, MANAGE_DEVICE_POLICY_LOCALE);
+ UserManager.DISALLOW_OUTGOING_BEAM, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_DATE_TIME, MANAGE_DEVICE_POLICY_TIME);
+ UserManager.DISALLOW_OUTGOING_CALLS, new String[]{MANAGE_DEVICE_POLICY_CALLS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+ UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_BRIGHTNESS, MANAGE_DEVICE_POLICY_DISPLAY);
+ UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+ UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CHANGE_WIFI_STATE, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CAMERA_TOGGLE, MANAGE_DEVICE_POLICY_CAMERA);
+ UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CAMERA, MANAGE_DEVICE_POLICY_CAMERA);
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_BLUETOOTH_SHARING, MANAGE_DEVICE_POLICY_BLUETOOTH);
+ UserManager.DISALLOW_SHARE_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_BIOMETRIC, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+ UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_AUTOFILL, MANAGE_DEVICE_POLICY_AUTOFILL);
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_APPS_CONTROL, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_AMBIENT_DISPLAY, MANAGE_DEVICE_POLICY_DISPLAY);
+ UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_AIRPLANE_MODE);
+ UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADJUST_VOLUME, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+ UserManager.DISALLOW_UNMUTE_DEVICE, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_WIFI_CONFIG, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_UNMUTE_MICROPHONE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_USER, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_CLONE_PROFILE, MANAGE_DEVICE_POLICY_PROFILES);
+ UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, MANAGE_DEVICE_POLICY_PROFILES);
+ UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CELLULAR_2G, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_WIFI_TETHERING, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+ UserManager.ENSURE_VERIFY_APPS, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
// Restrictions not allowed to be set by admins.
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_RECORD_AUDIO, null);
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_WALLPAPER, null);
- USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_DEFAULT_APPS, null);
}
private EnforcingAdmin enforcePermissionForUserRestriction(ComponentName who,
String userRestriction, String callerPackageName, int userId) {
- String permission = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
- if (permission != null) {
- return enforcePermissionAndGetEnforcingAdmin(who, permission, callerPackageName,
- userId);
- }
+ String[] permissions = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
+ if (permissions.length > 0) {
+ try {
+ return enforcePermissionsAndGetEnforcingAdmin(who, permissions, callerPackageName,
+ userId);
+ } catch (SecurityException e) {
+ throw new SecurityException("Caller does not hold the required permission for this "
+ + "user restriction: " + userRestriction + ".\n" + e.getMessage());
+ }
+ }
throw new SecurityException("Admins are not permitted to set User Restriction: "
+ userRestriction);
}
@@ -14045,9 +14047,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
if (isPolicyEngineForFinanceFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
who,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ new String[]{
+ MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL
+ },
caller.getPackageName(),
caller.getUserId());
mDevicePolicyEngine.setLocalPolicy(
@@ -22446,6 +22451,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
});
}
+ // Permission that will need to be created in V.
+ private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL =
+ "manage_device_policy_block_uninstall";
+ private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE =
+ "manage_device_policy_camera_toggle";
+ private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE =
+ "manage_device_policy_microphone_toggle";
+
// DPC types
private static final int DEFAULT_DEVICE_OWNER = 0;
private static final int FINANCED_DEVICE_OWNER = 1;
@@ -22669,7 +22682,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
{
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS);
- DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, DELEGATION_BLOCK_UNINSTALL);
+ DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL);
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS);
}
@@ -22748,6 +22761,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA_TOGGLE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
@@ -22764,6 +22781,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES,
@@ -22794,13 +22813,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user. Only one permission provided in the list needs to be granted to pass this
+ * check.
+ * The given permissions will be checked along with their associated cross-user permissions if
+ * they exists and the target user is different to the calling user.
+ * Returns an {@link EnforcingAdmin} for the caller.
+ *
+ * @param admin the component name of the admin.
+ * @param callerPackageName The package name of the calling application.
+ * @param permissions an array of permission names to be checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private EnforcingAdmin enforcePermissionsAndGetEnforcingAdmin(@Nullable ComponentName admin,
+ String[] permissions, String callerPackageName, int targetUserId) {
+ enforcePermissions(permissions, callerPackageName, targetUserId);
+ return getEnforcingAdminForCaller(admin, callerPackageName);
+ }
+
+ /**
+ * Checks if the calling process has been granted permission to apply a device policy on a
* specific user.
* The given permission will be checked along with its associated cross-user permission if it
* exists and the target user is different to the calling user.
* Returns an {@link EnforcingAdmin} for the caller.
*
* @param admin the component name of the admin.
- * @param callerPackageName The package name of the calling application.
+ * @param callerPackageName The package name of the calling application.
* @param permission The name of the permission being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
@@ -22858,6 +22898,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
new HashMap<>();
/**
+ * Checks if the calling process has been granted permission to apply a device policy.
+ *
+ * @param callerPackageName The package name of the calling application.
+ * @param permission The name of the permission being checked.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private void enforcePermission(String permission, String callerPackageName)
+ throws SecurityException {
+ if (!hasPermission(permission, callerPackageName)) {
+ throw new SecurityException("Caller does not have the required permissions for "
+ + "this user. Permission required: "
+ + permission
+ + ".");
+ }
+ }
+
+
+ /**
* Checks if the calling process has been granted permission to apply a device policy on a
* specific user.
* The given permission will be checked along with its associated cross-user permission if it
@@ -22871,17 +22930,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*/
private void enforcePermission(String permission, String callerPackageName, int targetUserId)
throws SecurityException {
- if (!hasPermission(permission, callerPackageName, targetUserId)) {
- // TODO(b/276920002): Split the error messages so that the cross-user permission
- // is only mentioned when it is needed.
+ enforcePermission(permission, callerPackageName);
+ if (targetUserId != getCallerIdentity(callerPackageName).getUserId()) {
+ enforcePermission(CROSS_USER_PERMISSIONS.get(permission), callerPackageName);
+ }
+ }
+
+ /**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user. Only one of the given permissions will be required to be held to pass this
+ * check.
+ * The given permissions will be checked along with their associated cross-user permissions if
+ * they exist and the target user is different to the calling user.
+ *
+ * @param permissions An array of the names of the permissions being checked.
+ * @param callerPackageName The package name of the calling application.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private void enforcePermissions(String[] permissions, String callerPackageName,
+ int targetUserId) throws SecurityException {
+ String heldPermission = "";
+ for (String permission : permissions) {
+ if (hasPermission(permission, callerPackageName)) {
+ heldPermission = permission;
+ break;
+ }
+ }
+ if (heldPermission.isEmpty()) {
throw new SecurityException("Caller does not have the required permissions for "
- + "this user. Permissions required: {"
- + permission
- + ", "
- + CROSS_USER_PERMISSIONS.get(permission)
- + "(if calling cross-user)"
- + "}");
+ + "this user. One of the following permission required: "
+ + Arrays.toString(permissions));
}
+ enforcePermission(heldPermission, callerPackageName, targetUserId);
}
/**
@@ -22891,25 +22973,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* exists and the target user is different to the calling user.
*
* @param callerPackageName The package name of the calling application.
+ * @param adminPolicy The admin policy that should grant holders permission.
* @param permission The name of the permission being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
* the associated cross-user permission if the caller's user is different to the target user.
*/
private void enforcePermission(String permission, int adminPolicy,
- String callerPackageName, int targetUserId)
- throws SecurityException {
- if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) {
- // TODO(b/276920002): Split the error messages so that the cross-user permission
- // is only mentioned when it is needed.
- throw new SecurityException("Caller does not have the required permissions for "
- + "this user. Permissions required: {"
- + permission
- + ", "
- + CROSS_USER_PERMISSIONS.get(permission)
- + "(if calling cross-user)"
- + "}");
+ String callerPackageName, int targetUserId) throws SecurityException {
+ if (hasAdminPolicy(adminPolicy, callerPackageName)) {
+ return;
}
+ enforcePermission(permission, callerPackageName, targetUserId);
}
/**
@@ -22933,6 +23008,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
enforcePermission(permission, callerPackageName, targetUserId);
}
+ private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) {
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
+ return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
+ }
+
/**
* Return whether the calling process has been granted permission to apply a device policy on
* a specific user.
@@ -22945,24 +23026,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
CallerIdentity caller = getCallerIdentity(callerPackageName);
boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName());
boolean hasPermissionOnTargetUser = true;
- if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) {
- hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission),
- caller.getPackageName());
+ if (hasPermissionOnOwnUser && caller.getUserId() != targetUserId) {
+ hasPermissionOnTargetUser = hasPermissionOnTargetUser
+ && hasPermission(CROSS_USER_PERMISSIONS.get(permission),
+ caller.getPackageName());
}
return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
}
- private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName,
- int adminPolicy, int targetUserId) {
- CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (hasPermission(permission, caller.getPackageName(), targetUserId)) {
- return true;
- }
- ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
- return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
- }
-
/**
* Return whether the calling process has been granted the given permission.
*
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index ec177c9ac33d..bc3f5ab5c42b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -237,9 +237,23 @@ public final class BroadcastQueueModernImplTest {
}
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+ BroadcastRecord record, int recordIndex) {
+ enqueueOrReplaceBroadcast(queue, record, recordIndex, false, 42_000_000L);
+ }
+
+ private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
BroadcastRecord record, int recordIndex, long enqueueTime) {
- queue.enqueueOrReplaceBroadcast(record, recordIndex, false);
+ enqueueOrReplaceBroadcast(queue, record, recordIndex, false, enqueueTime);
+ }
+
+ private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+ BroadcastRecord record, int recordIndex, boolean wouldBeSkipped, long enqueueTime) {
+ queue.enqueueOrReplaceBroadcast(record, recordIndex, wouldBeSkipped, (r, i) -> {
+ throw new UnsupportedOperationException();
+ });
record.enqueueTime = enqueueTime;
+ record.enqueueRealTime = enqueueTime;
+ record.enqueueClockTime = enqueueTime;
}
@Test
@@ -370,7 +384,7 @@ public final class BroadcastQueueModernImplTest {
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
List.of(makeMockRegisteredReceiver()), false);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
queue.setProcessAndUidCached(null, false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -397,7 +411,7 @@ public final class BroadcastQueueModernImplTest {
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
List.of(makeMockRegisteredReceiver()), false);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
queue.setProcessAndUidCached(null, false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -421,12 +435,12 @@ public final class BroadcastQueueModernImplTest {
// enqueue a bg-priority broadcast then a fg-priority one
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
- queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, timezoneRecord, 0);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -457,7 +471,7 @@ public final class BroadcastQueueModernImplTest {
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 1);
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -481,7 +495,7 @@ public final class BroadcastQueueModernImplTest {
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
@@ -507,11 +521,11 @@ public final class BroadcastQueueModernImplTest {
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, lazyRecord, 0);
assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
- queue.enqueueOrReplaceBroadcast(testRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, testRecord, 0);
assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
}
@@ -573,22 +587,22 @@ public final class BroadcastQueueModernImplTest {
BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
- queue.enqueueOrReplaceBroadcast(
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
@@ -1164,8 +1178,8 @@ public final class BroadcastQueueModernImplTest {
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
- List.of(makeMockRegisteredReceiver())), 0, false);
+ enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+ List.of(makeMockRegisteredReceiver())), 0);
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
// Make the foreground broadcast as active.
@@ -1176,15 +1190,15 @@ public final class BroadcastQueueModernImplTest {
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane,
- List.of(makeMockRegisteredReceiver())), 0, false);
+ enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(airplane,
+ List.of(makeMockRegisteredReceiver())), 0);
// Make the background broadcast as active.
queue.makeActiveNextPending();
assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
- queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
- List.of(makeMockRegisteredReceiver())), 0, false);
+ enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+ List.of(makeMockRegisteredReceiver())), 0);
// Even though the active broadcast is not a foreground one, scheduling group will be
// DEFAULT since there is a foreground broadcast waiting to be delivered.
assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 7468901a16af..d5d06d3b4eb8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -148,6 +150,28 @@ public class FaceAuthenticationClientTest {
}
@Test
+ public void testLockoutEndsOperation() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onLockoutPermanent();
+
+ verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+ eq(FACE_ERROR_LOCKOUT_PERMANENT), anyInt());
+ verify(mCallback).onClientFinished(client, false);
+ }
+
+ @Test
+ public void testTemporaryLockoutEndsOperation() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onLockoutTimed(1000);
+
+ verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+ eq(FACE_ERROR_LOCKOUT), anyInt());
+ verify(mCallback).onClientFinished(client, false);
+ }
+
+ @Test
public void notifyHalWhenContextChanges() throws RemoteException {
final FaceAuthenticationClient client = createClient();
client.start(mCallback);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 1e2fdec07d00..43b429c76749 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1410,9 +1410,9 @@ public class TransitionTests extends WindowTestsBase {
final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
assertNotNull(task1ChangeInfo);
assertTrue(task1ChangeInfo.hasChanged());
+ // Make sure the unrelated activity is NOT collected.
final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
- assertNotNull(activity1ChangeInfo);
- assertTrue(activity1ChangeInfo.hasChanged());
+ assertNull(activity1ChangeInfo);
// No need to wait for the activity in transient hide task.
assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
@@ -1442,6 +1442,7 @@ public class TransitionTests extends WindowTestsBase {
}
}
});
+ assertTrue(activity1.isVisible());
controller.finishTransition(closeTransition);
assertTrue(wasInFinishingTransition[0]);
assertNull(controller.mFinishingTransition);
@@ -1450,6 +1451,7 @@ public class TransitionTests extends WindowTestsBase {
assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
// Because task1 is occluded by task2, finishTransition should make activity1 invisible.
assertFalse(activity1.isVisibleRequested());
+ // Make sure activity1 visibility was committed
assertFalse(activity1.isVisible());
assertFalse(activity1.app.hasActivityInVisibleTask());
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 337e1f92050c..7fe8582f96de 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -27,6 +27,8 @@ import android.util.Slog;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.audio.AudioService;
+import java.util.Arrays;
+
/**
* Represents the ALSA specification, and attributes of an ALSA device.
*/
@@ -36,17 +38,21 @@ public final class UsbAlsaDevice {
private final int mCardNum;
private final int mDeviceNum;
+ private final String mAlsaCardDeviceString;
private final String mDeviceAddress;
- private final boolean mHasOutput;
- private final boolean mHasInput;
- private final boolean mIsInputHeadset;
- private final boolean mIsOutputHeadset;
- private final boolean mIsDock;
+ // The following two constant will be used as index to access arrays.
+ private static final int INPUT = 0;
+ private static final int OUTPUT = 1;
+ private static final int NUM_DIRECTIONS = 2;
+ private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
+ private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];
- private boolean mSelected = false;
- private int mOutputState;
- private int mInputState;
+ private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
+ private final boolean mIsDock;
+ private final int[] mDeviceType = new int[NUM_DIRECTIONS];
+ private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
+ private int[] mState = new int[NUM_DIRECTIONS];
private UsbAlsaJackDetector mJackDetector;
private IAudioService mAudioService;
@@ -60,11 +66,13 @@ public final class UsbAlsaDevice {
mCardNum = card;
mDeviceNum = device;
mDeviceAddress = deviceAddress;
- mHasOutput = hasOutput;
- mHasInput = hasInput;
- mIsInputHeadset = isInputHeadset;
- mIsOutputHeadset = isOutputHeadset;
+ mHasDevice[OUTPUT] = hasOutput;
+ mHasDevice[INPUT] = hasInput;
+ mIsHeadset[INPUT] = isInputHeadset;
+ mIsHeadset[OUTPUT] = isOutputHeadset;
mIsDock = isDock;
+ initDeviceType();
+ mAlsaCardDeviceString = getAlsaCardDeviceString();
}
/**
@@ -104,28 +112,28 @@ public final class UsbAlsaDevice {
* @return true if the device supports output.
*/
public boolean hasOutput() {
- return mHasOutput;
+ return mHasDevice[OUTPUT];
}
/**
* @return true if the device supports input (recording).
*/
public boolean hasInput() {
- return mHasInput;
+ return mHasDevice[INPUT];
}
/**
- * @return true if the device is a headset for purposes of input.
+ * @return true if the device is a headset for purposes of output.
*/
- public boolean isInputHeadset() {
- return mIsInputHeadset;
+ public boolean isOutputHeadset() {
+ return mIsHeadset[OUTPUT];
}
/**
- * @return true if the device is a headset for purposes of output.
+ * @return true if the device is a headset for purposes of input.
*/
- public boolean isOutputHeadset() {
- return mIsOutputHeadset;
+ public boolean isInputHeadset() {
+ return mIsHeadset[INPUT];
}
/**
@@ -157,6 +165,9 @@ public final class UsbAlsaDevice {
/** Begins a jack-detection thread. */
private synchronized void startJackDetect() {
+ if (mJackDetector != null) {
+ return;
+ }
// If no jack detect capabilities exist, mJackDetector will be null.
mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
}
@@ -171,75 +182,152 @@ public final class UsbAlsaDevice {
/** Start using this device as the selected USB Audio Device. */
public synchronized void start() {
- mSelected = true;
- mInputState = 0;
- mOutputState = 0;
+ startInput();
+ startOutput();
+ }
+
+ /** Start using this device as the selected USB input device. */
+ public synchronized void startInput() {
+ startDevice(INPUT);
+ }
+
+ /** Start using this device as selected USB output device. */
+ public synchronized void startOutput() {
+ startDevice(OUTPUT);
+ }
+
+ private void startDevice(int direction) {
+ if (mIsSelected[direction]) {
+ return;
+ }
+ mIsSelected[direction] = true;
+ mState[direction] = 0;
startJackDetect();
- updateWiredDeviceConnectionState(true);
+ updateWiredDeviceConnectionState(direction, true /*enable*/);
}
/** Stop using this device as the selected USB Audio Device. */
public synchronized void stop() {
- stopJackDetect();
- updateWiredDeviceConnectionState(false);
- mSelected = false;
+ stopInput();
+ stopOutput();
}
- /** Updates AudioService with the connection state of the alsaDevice.
- * Checks ALSA Jack state for inputs and outputs before reporting.
- */
- public synchronized void updateWiredDeviceConnectionState(boolean enable) {
- if (!mSelected) {
- Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
+ /** Stop using this device as the selected USB input device. */
+ public synchronized void stopInput() {
+ if (!mIsSelected[INPUT]) {
return;
}
- String alsaCardDeviceString = getAlsaCardDeviceString();
- if (alsaCardDeviceString == null) {
+ if (!mIsSelected[OUTPUT]) {
+ // Stop jack detection when both input and output are stopped
+ stopJackDetect();
+ }
+ updateInputWiredDeviceConnectionState(false /*enable*/);
+ mIsSelected[INPUT] = false;
+ }
+
+ /** Stop using this device as the selected USB output device. */
+ public synchronized void stopOutput() {
+ if (!mIsSelected[OUTPUT]) {
return;
}
- try {
- // Output Device
- if (mHasOutput) {
- int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
- : (mIsOutputHeadset
- ? AudioSystem.DEVICE_OUT_USB_HEADSET
- : AudioSystem.DEVICE_OUT_USB_DEVICE);
- if (DEBUG) {
- Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
- + " addr:" + alsaCardDeviceString
- + " name:" + mDeviceName);
- }
- boolean connected = isOutputJackConnected();
- Slog.i(TAG, "OUTPUT JACK connected: " + connected);
- int outputState = (enable && connected) ? 1 : 0;
- if (outputState != mOutputState) {
- mOutputState = outputState;
- AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
- alsaCardDeviceString, mDeviceName);
- mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG);
- }
- }
+ if (!mIsSelected[INPUT]) {
+ // Stop jack detection when both input and output are stopped
+ stopJackDetect();
+ }
+ updateOutputWiredDeviceConnectionState(false /*enable*/);
+ mIsSelected[OUTPUT] = false;
+ }
+
+ private void initDeviceType() {
+ mDeviceType[INPUT] = mHasDevice[INPUT]
+ ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
+ : AudioSystem.DEVICE_IN_USB_DEVICE)
+ : AudioSystem.DEVICE_NONE;
+ mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
+ ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+ : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
+ : AudioSystem.DEVICE_OUT_USB_DEVICE))
+ : AudioSystem.DEVICE_NONE;
+ }
- // Input Device
- if (mHasInput) {
- int device = mIsInputHeadset
- ? AudioSystem.DEVICE_IN_USB_HEADSET
- : AudioSystem.DEVICE_IN_USB_DEVICE;
- boolean connected = isInputJackConnected();
- Slog.i(TAG, "INPUT JACK connected: " + connected);
- int inputState = (enable && connected) ? 1 : 0;
- if (inputState != mInputState) {
- mInputState = inputState;
- AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
- alsaCardDeviceString, mDeviceName);
- mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG);
- }
+ /**
+ * @return the output device type that will be used to notify AudioService about device
+ * connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
+ * will be returned.
+ */
+ public int getOutputDeviceType() {
+ return mDeviceType[OUTPUT];
+ }
+
+ /**
+ * @return the input device type that will be used to notify AudioService about device
+ * connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
+ * will be returned.
+ */
+ public int getInputDeviceType() {
+ return mDeviceType[INPUT];
+ }
+
+ private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
+ if (!mIsSelected[direction]) {
+ Slog.e(TAG, "Updating wired device connection state on unselected device");
+ return false;
+ }
+ if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
+ Slog.d(TAG,
+ "Unable to set device connection state as " + DIRECTION_STR[direction]
+ + " device type is none");
+ return false;
+ }
+ if (mAlsaCardDeviceString == null) {
+ Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
+ + "state failed as alsa card device string is null");
+ return false;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
+ + " addr:" + mAlsaCardDeviceString
+ + " name:" + mDeviceName);
+ }
+ boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
+ Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
+ int state = (enable && connected) ? 1 : 0;
+ if (state != mState[direction]) {
+ mState[direction] = state;
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+ mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
+ try {
+ mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
+ + DIRECTION_STR[direction]);
+ return false;
}
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
}
+ return true;
}
+ /**
+ * Notify AudioService about the input device connection state.
+ *
+ * @param enable true to notify the device as connected.
+ * @return true only when it successfully notifies AudioService about the device
+ * connection state.
+ */
+ public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
+ return updateWiredDeviceConnectionState(INPUT, enable);
+ }
+
+ /**
+ * Notify AudioService about the output device connection state.
+ *
+ * @param enable true to notify the device as connected.
+ * @return true only when it successfully notifies AudioService about the device
+ * connection state.
+ */
+ public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
+ return updateWiredDeviceConnectionState(OUTPUT, enable);
+ }
/**
* @Override
@@ -249,8 +337,8 @@ public final class UsbAlsaDevice {
return "UsbAlsaDevice: [card: " + mCardNum
+ ", device: " + mDeviceNum
+ ", name: " + mDeviceName
- + ", hasOutput: " + mHasOutput
- + ", hasInput: " + mHasInput + "]";
+ + ", hasOutput: " + mHasDevice[OUTPUT]
+ + ", hasInput: " + mHasDevice[INPUT] + "]";
}
/**
@@ -262,8 +350,8 @@ public final class UsbAlsaDevice {
dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
- dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput);
- dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput);
+ dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]);
+ dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);
dump.end(token);
@@ -294,10 +382,8 @@ public final class UsbAlsaDevice {
UsbAlsaDevice other = (UsbAlsaDevice) obj;
return (mCardNum == other.mCardNum
&& mDeviceNum == other.mDeviceNum
- && mHasOutput == other.mHasOutput
- && mHasInput == other.mHasInput
- && mIsInputHeadset == other.mIsInputHeadset
- && mIsOutputHeadset == other.mIsOutputHeadset
+ && Arrays.equals(mHasDevice, other.mHasDevice)
+ && Arrays.equals(mIsHeadset, other.mIsHeadset)
&& mIsDock == other.mIsDock);
}
@@ -310,10 +396,10 @@ public final class UsbAlsaDevice {
int result = 1;
result = prime * result + mCardNum;
result = prime * result + mDeviceNum;
- result = prime * result + (mHasOutput ? 0 : 1);
- result = prime * result + (mHasInput ? 0 : 1);
- result = prime * result + (mIsInputHeadset ? 0 : 1);
- result = prime * result + (mIsOutputHeadset ? 0 : 1);
+ result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1);
+ result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
+ result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
+ result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
result = prime * result + (mIsDock ? 0 : 1);
return result;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
index c4988478df71..d4f0b59dd7f2 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
@@ -81,7 +81,8 @@ public final class UsbAlsaJackDetector implements Runnable {
if (mStopJackDetect) {
return false;
}
- mAlsaDevice.updateWiredDeviceConnectionState(true);
+ mAlsaDevice.updateOutputWiredDeviceConnectionState(true);
+ mAlsaDevice.updateInputWiredDeviceConnectionState(true);
}
return true;
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index aa1d556d02d3..99881e194b07 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -20,12 +20,14 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.usb.UsbDevice;
+import android.media.AudioManager;
import android.media.IAudioService;
import android.media.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.service.usb.UsbAlsaManagerProto;
import android.util.Slog;
@@ -42,6 +44,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Stack;
/**
* UsbAlsaManager manages USB audio and MIDI devices.
@@ -51,8 +54,9 @@ public final class UsbAlsaManager {
private static final boolean DEBUG = false;
// Flag to turn on/off multi-peripheral select mode
- // Set to true to have single-device-only mode
- private static final boolean mIsSingleMode = true;
+ // Set to true to have multi-devices mode
+ private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean(
+ "ro.audio.multi_usb_mode", false /*def*/);
private static final String ALSA_DIRECTORY = "/dev/snd/";
@@ -70,7 +74,11 @@ public final class UsbAlsaManager {
// this is needed to map USB devices to ALSA Audio Devices, especially to remove an
// ALSA device when we are notified that its associated USB device has been removed.
private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
- private UsbAlsaDevice mSelectedDevice;
+ // A map from device type to attached devices. Given the audio framework only supports
+ // single device connection per device type, only the last attached device will be
+ // connected to audio framework. Once the last device is removed, previous device can
+ // be connected to audio framework.
+ private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>();
//
// Device Denylist
@@ -162,11 +170,6 @@ public final class UsbAlsaManager {
Slog.d(TAG, "selectAlsaDevice() " + alsaDevice);
}
- // This must be where an existing USB audio device is deselected.... (I think)
- if (mIsSingleMode && mSelectedDevice != null) {
- deselectAlsaDevice();
- }
-
// FIXME Does not yet handle the case where the setting is changed
// after device connection. Ideally we should handle the settings change
// in SettingsObserver. Here we should log that a USB device is connected
@@ -178,21 +181,18 @@ public final class UsbAlsaManager {
return;
}
- mSelectedDevice = alsaDevice;
alsaDevice.start();
+
if (DEBUG) {
Slog.d(TAG, "selectAlsaDevice() - done.");
}
}
- private synchronized void deselectAlsaDevice() {
+ private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) {
if (DEBUG) {
- Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice);
- }
- if (mSelectedDevice != null) {
- mSelectedDevice.stop();
- mSelectedDevice = null;
+ Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice);
}
+ selectedDevice.stop();
}
private int getAlsaDeviceListIndexFor(String deviceAddress) {
@@ -204,32 +204,86 @@ public final class UsbAlsaManager {
return -1;
}
- private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
+ private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+ if (deviceType == AudioManager.DEVICE_NONE) {
+ Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device);
+ return;
+ }
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null) {
+ mAttachedDevices.put(deviceType, new Stack<>());
+ devices = mAttachedDevices.get(deviceType);
+ }
+ devices.push(device);
+ }
+
+ private void addAlsaDevice(UsbAlsaDevice device) {
+ mAlsaDevices.add(0, device);
+ addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device);
+ addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device);
+ }
+
+ private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null) {
+ return;
+ }
+ devices.remove(device);
+ if (devices.isEmpty()) {
+ mAttachedDevices.remove(deviceType);
+ }
+ }
+
+ private UsbAlsaDevice removeAlsaDevice(String deviceAddress) {
int index = getAlsaDeviceListIndexFor(deviceAddress);
if (index > -1) {
- return mAlsaDevices.remove(index);
+ UsbAlsaDevice device = mAlsaDevices.remove(index);
+ removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device);
+ removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device);
+ return device;
} else {
return null;
}
}
- /* package */ UsbAlsaDevice selectDefaultDevice() {
+ private UsbAlsaDevice selectDefaultDevice(int deviceType) {
if (DEBUG) {
- Slog.d(TAG, "selectDefaultDevice()");
+ Slog.d(TAG, "selectDefaultDevice():" + deviceType);
}
- if (mAlsaDevices.size() > 0) {
- UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
- if (DEBUG) {
- Slog.d(TAG, " alsaDevice:" + alsaDevice);
- }
- if (alsaDevice != null) {
- selectAlsaDevice(alsaDevice);
- }
- return alsaDevice;
- } else {
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null || devices.isEmpty()) {
return null;
}
+ UsbAlsaDevice alsaDevice = devices.peek();
+ Slog.d(TAG, "select default device:" + alsaDevice);
+ if (AudioManager.isInputDevice(deviceType)) {
+ alsaDevice.startInput();
+ } else {
+ alsaDevice.startOutput();
+ }
+ return alsaDevice;
+ }
+
+ private void deselectCurrentDevice(int deviceType) {
+ if (DEBUG) {
+ Slog.d(TAG, "deselectCurrentDevice():" + deviceType);
+ }
+ if (deviceType == AudioManager.DEVICE_NONE) {
+ return;
+ }
+
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null || devices.isEmpty()) {
+ return;
+ }
+ UsbAlsaDevice alsaDevice = devices.peek();
+ Slog.d(TAG, "deselect current device:" + alsaDevice);
+ if (AudioManager.isInputDevice(deviceType)) {
+ alsaDevice.stopInput();
+ } else {
+ alsaDevice.stopOutput();
+ }
}
/* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
@@ -246,6 +300,7 @@ public final class UsbAlsaManager {
AlsaCardsParser.AlsaCardRecord cardRec =
mCardsParser.findCardNumFor(deviceAddress);
if (cardRec == null) {
+ Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
return;
}
@@ -275,12 +330,19 @@ public final class UsbAlsaManager {
new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
deviceAddress, hasOutput, hasInput,
isInputHeadset, isOutputHeadset, isDock);
- if (alsaDevice != null) {
- alsaDevice.setDeviceNameAndDescription(
- cardRec.getCardName(), cardRec.getCardDescription());
- mAlsaDevices.add(0, alsaDevice);
- selectAlsaDevice(alsaDevice);
+ alsaDevice.setDeviceNameAndDescription(
+ cardRec.getCardName(), cardRec.getCardDescription());
+ if (IS_MULTI_MODE) {
+ deselectCurrentDevice(alsaDevice.getInputDeviceType());
+ deselectCurrentDevice(alsaDevice.getOutputDeviceType());
+ } else {
+ // At single mode, the first device is the selected device.
+ if (!mAlsaDevices.isEmpty()) {
+ deselectAlsaDevice(mAlsaDevices.get(0));
+ }
}
+ addAlsaDevice(alsaDevice);
+ selectAlsaDevice(alsaDevice);
}
addMidiDevice(deviceAddress, usbDevice, parser, cardRec);
@@ -346,12 +408,20 @@ public final class UsbAlsaManager {
}
// Audio
- UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
+ UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress);
Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
- if (alsaDevice != null && alsaDevice == mSelectedDevice) {
+ if (alsaDevice != null) {
waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
- deselectAlsaDevice();
- selectDefaultDevice(); // if there any external devices left, select one of them
+ deselectAlsaDevice(alsaDevice);
+ if (IS_MULTI_MODE) {
+ selectDefaultDevice(alsaDevice.getOutputDeviceType());
+ selectDefaultDevice(alsaDevice.getInputDeviceType());
+ } else {
+ // If there are any external devices left, select the latest attached one
+ if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) {
+ selectAlsaDevice(mAlsaDevices.get(0));
+ }
+ }
}
// MIDI
@@ -362,7 +432,6 @@ public final class UsbAlsaManager {
}
logDevices("usbDeviceRemoved()");
-
}
/* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 5efd158133ed..07dc1c66bc4d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -315,12 +315,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
int keyphraseId, boolean runInBatterySaverMode) {
synchronized (mLock) {
+ // TODO Remove previous callback handling
IRecognitionStatusCallback oldCallback = modelData.getCallback();
if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
Slog.w(TAG, "Canceling previous recognition for model id: "
+ modelData.getModelId());
try {
- oldCallback.onError(STATUS_ERROR);
+ oldCallback.onPreempted();
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onDetectionStopped", e);
}
@@ -759,15 +760,12 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
onRecognitionAbortLocked(event);
break;
case SoundTrigger.RECOGNITION_STATUS_FAILURE:
- // Fire failures to all listeners since it's not tied to a keyphrase.
- onRecognitionFailureLocked();
- break;
case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
if (isKeyphraseRecognitionEvent(event)) {
- onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+ onKeyphraseRecognitionLocked((KeyphraseRecognitionEvent) event);
} else {
- onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+ onGenericRecognitionLocked((GenericRecognitionEvent) event);
}
break;
}
@@ -778,7 +776,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return event instanceof KeyphraseRecognitionEvent;
}
- private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+ private void onGenericRecognitionLocked(GenericRecognitionEvent event) {
MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
&& event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
@@ -901,17 +899,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
- private void onRecognitionFailureLocked() {
- Slog.w(TAG, "Recognition failure");
- MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
- try {
- sendErrorCallbacksToAllLocked(STATUS_ERROR);
- } finally {
- internalClearModelStateLocked();
- internalClearGlobalStateLocked();
- }
- }
-
private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
if (event == null) {
Slog.w(TAG, "Null RecognitionEvent received.");
@@ -927,7 +914,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return keyphraseExtras[0].id;
}
- private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+ private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) {
Slog.i(TAG, "Recognition success");
MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
int keyphraseId = getKeyphraseIdFromEvent(event);
@@ -1001,7 +988,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private void onServiceDiedLocked() {
try {
MetricsLogger.count(mContext, "sth_service_died", 1);
- sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
+ for (ModelData modelData : mModelDataMap.values()) {
+ IRecognitionStatusCallback callback = modelData.getCallback();
+ if (callback != null) {
+ try {
+ callback.onModuleDied();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException send moduleDied for model handle " +
+ modelData.getHandle(), e);
+ }
+ }
+ }
} finally {
internalClearModelStateLocked();
internalClearGlobalStateLocked();
@@ -1111,21 +1108,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
- // Sends an error callback to all models with a valid registered callback.
- private void sendErrorCallbacksToAllLocked(int errorCode) {
- for (ModelData modelData : mModelDataMap.values()) {
- IRecognitionStatusCallback callback = modelData.getCallback();
- if (callback != null) {
- try {
- callback.onError(errorCode);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
- modelData.getHandle(), e);
- }
- }
- }
- }
-
/**
* Stops and unloads all models. This is intended as a clean-up call with the expectation that
* this instance is not used after.
@@ -1342,11 +1324,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
// Notify of error if needed.
if (notifyClientOnError) {
try {
- callback.onError(status);
+ callback.onResumeFailed(status);
} catch (DeadObjectException e) {
forceStopAndUnloadModelLocked(modelData, e);
} catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
+ Slog.w(TAG, "RemoteException in onResumeFailed", e);
}
}
} else {
@@ -1382,15 +1364,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
status = mModule.stopRecognition(modelData.getHandle());
if (status != SoundTrigger.STATUS_OK) {
- Slog.w(TAG, "stopRecognition call failed with " + status);
+ Slog.e(TAG, "stopRecognition call failed with " + status);
MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
if (notify) {
try {
- callback.onError(status);
+ callback.onPauseFailed(status);
} catch (DeadObjectException e) {
forceStopAndUnloadModelLocked(modelData, e);
} catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
+ Slog.w(TAG, "RemoteException in onPauseFailed", e);
}
}
} else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 203a3e74d9da..1bbea89f5acb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -25,6 +25,7 @@ import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_SERVICES;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_DEAD_OBJECT;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
@@ -1388,8 +1389,7 @@ public class SoundTriggerService extends SystemService {
}));
}
- @Override
- public void onError(int status) {
+ private void onError(int status) {
if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
@@ -1412,6 +1412,30 @@ public class SoundTriggerService extends SystemService {
}
@Override
+ public void onPreempted() {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onPreempted");
+ onError(STATUS_ERROR);
+ }
+
+ @Override
+ public void onModuleDied() {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onModuleDied");
+ onError(STATUS_DEAD_OBJECT);
+ }
+
+ @Override
+ public void onResumeFailed(int status) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onResumeFailed: " + status);
+ onError(status);
+ }
+
+ @Override
+ public void onPauseFailed(int status) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onPauseFailed: " + status);
+ onError(status);
+ }
+
+ @Override
public void onRecognitionPaused() {
Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index f3cb9baedd4b..a1adee732701 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -56,6 +56,7 @@ import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.ISandboxedDetectionService;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.SoundTriggerFailure;
import android.service.voice.VisualQueryDetectionService;
import android.service.voice.VisualQueryDetectionServiceFailure;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
@@ -576,8 +577,31 @@ final class HotwordDetectionConnection {
}
@Override
- public void onError(int status) throws RemoteException {
- mExternalCallback.onError(status);
+ public void onPreempted() throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_UNEXPECTED_PREEMPTION,
+ "Unexpected startRecognition on already started ST session"));
+ }
+
+ @Override
+ public void onModuleDied() throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_MODULE_DIED,
+ "STHAL died"));
+ }
+
+ @Override
+ public void onResumeFailed(int status) throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+ "STService recognition resume failed with: " + status));
+ }
+
+ @Override
+ public void onPauseFailed(int status) throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+ "STService recognition pause failed with: " + status));
}
@Override