diff options
222 files changed, 5035 insertions, 3970 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index eac416a37c20..24cd61053c5e 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -641,6 +641,19 @@ java_aconfig_library { ], } +java_aconfig_library { + name: "android.permission.flags-aconfig-java-host", + aconfig_declarations: "android.permission.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.permission", + "com.android.nfcservices", + ], +} + // SQLite aconfig_declarations { name: "android.database.sqlite-aconfig", diff --git a/apct-tests/perftests/inputmethod/AndroidManifest.xml b/apct-tests/perftests/inputmethod/AndroidManifest.xml index 5dd6ccccfb1c..34f9692cc8ed 100644 --- a/apct-tests/perftests/inputmethod/AndroidManifest.xml +++ b/apct-tests/perftests/inputmethod/AndroidManifest.xml @@ -22,7 +22,7 @@ <application> <uses-library android:name="android.test.runner" /> <activity android:name="android.perftests.utils.PerfTestActivity" - android:theme="@android:style/Theme.DeviceDefault.NoActionBar" + android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar" android:exported="true"> <intent-filter> <action android:name="com.android.perftests.core.PERFTEST" /> diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 613678bedf8a..410074e6ec85 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -1989,6 +1989,9 @@ public class AppStandbyController mAdminProtectedPackages.put(userId, packageNames); } } + if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) { + postCheckIdleStates(userId); + } } @Override diff --git a/core/api/system-current.txt b/core/api/system-current.txt index bed8c4169a63..45bcd0dbbf21 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1145,7 +1145,6 @@ package android.app { public static final class StatusBarManager.DisableInfo implements android.os.Parcelable { method public boolean areAllComponentsEnabled(); method public int describeContents(); - method public boolean isBackDisabled(); method public boolean isNavigateToHomeDisabled(); method public boolean isNotificationPeekingDisabled(); method public boolean isRecentsDisabled(); diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index e6e46ddaa420..301fef877d6c 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -1448,7 +1448,6 @@ public class StatusBarManager { * * @hide */ - @SystemApi public boolean isBackDisabled() { return mBack; } @@ -1862,38 +1861,38 @@ public class StatusBarManager { }; @DataClass.Generated( - time = 1707345957771L, + time = 1708625947132L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/StatusBarManager.java", - inputSignatures = "private boolean mStatusBarExpansion\nprivate " - + "boolean mNavigateHome\nprivate boolean mNotificationPeeking\nprivate " - + "boolean mRecents\nprivate boolean mBack\nprivate boolean " - + "mSearch\nprivate boolean mSystemIcons\nprivate boolean mClock\nprivate" - + " boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n" + inputSignatures = "private boolean mStatusBarExpansion\nprivate boolean " + + "mNavigateHome\nprivate boolean mNotificationPeeking\nprivate " + + "boolean mRecents\nprivate boolean mBack\nprivate boolean mSearch\n" + + "private boolean mSystemIcons\nprivate boolean mClock\nprivate " + + "boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n" + "private boolean mNotificationTicker\npublic " + "@android.annotation.SystemApi boolean isStatusBarExpansionDisabled()\n" + "public void setStatusBarExpansionDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\n" - + "public void setNavigationHomeDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()\n" - + "public void setNotificationPeekingDisabled(boolean)\npublic " + + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\npublic" + + " void setNavigationHomeDisabled(boolean)\npublic " + + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()" + + "\npublic void setNotificationPeekingDisabled(boolean)\npublic " + "@android.annotation.SystemApi boolean isRecentsDisabled()\npublic " - + "void setRecentsDisabled(boolean)\npublic @android.annotation.SystemApi " - + "boolean isBackDisabled()\npublic void setBackDisabled(boolean)\npublic " + + "void setRecentsDisabled(boolean)\npublic boolean isBackDisabled()" + + "\npublic void setBackDisabled(boolean)\npublic " + "@android.annotation.SystemApi boolean isSearchDisabled()\npublic " + "void setSearchDisabled(boolean)\npublic boolean " - + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)" - + "\npublic boolean isClockDisabled()\npublic " - + "void setClockDisabled(boolean)\npublic " - + "boolean areNotificationIconsDisabled()\npublic " - + "void setNotificationIconsDisabled(boolean)\npublic boolean " + + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)\n" + + "public boolean isClockDisabled()\npublic " + + "void setClockDisabled(boolean)\npublic boolean " + + "areNotificationIconsDisabled()\npublic void " + + "setNotificationIconsDisabled(boolean)\npublic boolean " + "isNotificationTickerDisabled()\npublic void " + "setNotificationTickerDisabled(boolean)\npublic " + "@android.annotation.TestApi boolean isRotationSuggestionDisabled()\n" + "public void setRotationSuggestionDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\n" - + "public void setEnableAll()\npublic boolean areAllComponentsDisabled()" - + "\npublic void setDisableAll()\npublic @android.annotation.NonNull " + + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\npublic" + + " void setEnableAll()\npublic boolean areAllComponentsDisabled()\n" + + "public void setDisableAll()\npublic @android.annotation.NonNull " + "@java.lang.Override java.lang.String toString()\npublic " + "android.util.Pair<java.lang.Integer,java.lang.Integer> toFlags()\n" + "class DisableInfo extends java.lang.Object implements " diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 1aee9fe57466..a9f2d74eae39 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -317,11 +317,6 @@ public abstract class DevicePolicyManagerInternal { public abstract boolean isUserOrganizationManaged(@UserIdInt int userId); /** - * Returns whether the application exemptions feature flag is enabled. - */ - public abstract boolean isApplicationExemptionsFlagEnabled(); - - /** * Returns a map of admin to {@link Bundle} map of restrictions set by the admins for the * provided {@code packageName} in the provided {@code userId} */ diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 6a07484eebc6..ac843cbfbfac 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -195,6 +195,25 @@ flag { } } +flag { + name: "power_exemption_bg_usage_fix" + namespace: "enterprise" + description: "Ensure aps with EXEMPT_FROM_POWER_RESTRICTIONS can execute in the background" + bug: "333379020" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "disallow_user_control_bg_usage_fix" + namespace: "enterprise" + description: "Make DPM.setUserControlDisabledPackages() ensure background usage is allowed" + bug: "326031059" + metadata { + purpose: PURPOSE_BUGFIX + } +} flag { name: "esim_management_ux_enabled" @@ -228,6 +247,16 @@ flag { } flag { + name: "always_persist_do" + namespace: "enterprise" + description: "Always write device_owners2.xml so that migration flags aren't lost" + bug: "335232744" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "is_recursive_required_app_merging_enabled" namespace: "enterprise" description: "Guards a new flow for recursive required enterprise app list merging" diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index 722d5f0fe462..c9b4aa1456fd 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -23,6 +23,8 @@ import static com.android.window.flags.Flags.bundleClientTransactionFlag; import static java.util.Objects.requireNonNull; +import android.annotation.AnyThread; +import android.annotation.MainThread; import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityThread; @@ -94,6 +96,7 @@ public class ClientTransactionListenerController { * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and * {@link ActivityWindowInfo}. */ + @AnyThread public void registerActivityWindowInfoChangedListener( @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { if (!activityWindowInfoFlag()) { @@ -108,6 +111,7 @@ public class ClientTransactionListenerController { * Unregisters the listener that was previously registered via * {@link #registerActivityWindowInfoChangedListener(BiConsumer)} */ + @AnyThread public void unregisterActivityWindowInfoChangedListener( @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { if (!activityWindowInfoFlag()) { @@ -122,6 +126,7 @@ public class ClientTransactionListenerController { * Called when receives a {@link ClientTransaction} that is updating an activity's * {@link ActivityWindowInfo}. */ + @MainThread public void onActivityWindowInfoChanged(@NonNull IBinder activityToken, @NonNull ActivityWindowInfo activityWindowInfo) { if (!activityWindowInfoFlag()) { @@ -141,17 +146,20 @@ public class ClientTransactionListenerController { } /** Called when starts executing a remote {@link ClientTransaction}. */ + @MainThread public void onClientTransactionStarted() { mIsClientTransactionExecuting = true; } /** Called when finishes executing a remote {@link ClientTransaction}. */ + @MainThread public void onClientTransactionFinished() { notifyDisplayManagerIfNeeded(); mIsClientTransactionExecuting = false; } /** Called before updating the Configuration of the given {@code context}. */ + @MainThread public void onContextConfigurationPreChanged(@NonNull Context context) { if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) { // Not enable for system server. @@ -166,6 +174,7 @@ public class ClientTransactionListenerController { } /** Called after updating the Configuration of the given {@code context}. */ + @MainThread public void onContextConfigurationPostChanged(@NonNull Context context) { if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) { // Not enable for system server. diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 4963a4f27803..321e539816b2 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -171,6 +171,13 @@ flag { } flag { + name: "schedule_stop_of_background_user" + namespace: "multiuser" + description: "Schedule background users to be stopped at a future point." + bug: "330351042" +} + +flag { name: "disable_private_space_items_on_home" namespace: "profile_experiences" description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically" diff --git a/core/java/android/hardware/face/FaceCallback.java b/core/java/android/hardware/face/FaceCallback.java new file mode 100644 index 000000000000..b69024fd05fa --- /dev/null +++ b/core/java/android/hardware/face/FaceCallback.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2024 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 android.hardware.face; + +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR_BASE; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR_BASE; +import static android.hardware.face.FaceManager.getAuthHelpMessage; +import static android.hardware.face.FaceManager.getEnrollHelpMessage; +import static android.hardware.face.FaceManager.getErrorString; + +import android.content.Context; +import android.hardware.biometrics.CryptoObject; +import android.hardware.face.FaceManager.AuthenticationCallback; +import android.hardware.face.FaceManager.EnrollmentCallback; +import android.hardware.face.FaceManager.FaceDetectionCallback; +import android.hardware.face.FaceManager.GenerateChallengeCallback; +import android.hardware.face.FaceManager.GetFeatureCallback; +import android.hardware.face.FaceManager.RemovalCallback; +import android.hardware.face.FaceManager.SetFeatureCallback; +import android.util.Slog; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Encapsulates callbacks and client specific information for each face related request. + * @hide + */ +public class FaceCallback { + private static final String TAG = " FaceCallback"; + + @Nullable + private AuthenticationCallback mAuthenticationCallback; + @Nullable + private EnrollmentCallback mEnrollmentCallback; + @Nullable + private RemovalCallback mRemovalCallback; + @Nullable + private GenerateChallengeCallback mGenerateChallengeCallback; + @Nullable + private FaceDetectionCallback mFaceDetectionCallback; + @Nullable + private SetFeatureCallback mSetFeatureCallback; + @Nullable + private GetFeatureCallback mGetFeatureCallback; + @Nullable + private Face mRemovalFace; + @Nullable + private CryptoObject mCryptoObject; + + /** + * Construction for face authentication client callback. + */ + FaceCallback(AuthenticationCallback authenticationCallback, CryptoObject cryptoObject) { + mAuthenticationCallback = authenticationCallback; + mCryptoObject = cryptoObject; + } + + /** + * Construction for face detect client callback. + */ + FaceCallback(FaceDetectionCallback faceDetectionCallback) { + mFaceDetectionCallback = faceDetectionCallback; + } + + /** + * Construction for face enroll client callback. + */ + FaceCallback(EnrollmentCallback enrollmentCallback) { + mEnrollmentCallback = enrollmentCallback; + } + + /** + * Construction for face generate challenge client callback. + */ + FaceCallback(GenerateChallengeCallback generateChallengeCallback) { + mGenerateChallengeCallback = generateChallengeCallback; + } + + /** + * Construction for face set feature client callback. + */ + FaceCallback(SetFeatureCallback setFeatureCallback) { + mSetFeatureCallback = setFeatureCallback; + } + + /** + * Construction for face get feature client callback. + */ + FaceCallback(GetFeatureCallback getFeatureCallback) { + mGetFeatureCallback = getFeatureCallback; + } + + /** + * Construction for single face removal client callback. + */ + FaceCallback(RemovalCallback removalCallback, Face removalFace) { + mRemovalCallback = removalCallback; + mRemovalFace = removalFace; + } + + /** + * Construction for all face removal client callback. + */ + FaceCallback(RemovalCallback removalCallback) { + mRemovalCallback = removalCallback; + } + + /** + * Propagate set feature completed via the callback. + * @param success if the operation was completed successfully + * @param feature the feature that was set + */ + public void sendSetFeatureCompleted(boolean success, int feature) { + if (mSetFeatureCallback == null) { + return; + } + mSetFeatureCallback.onCompleted(success, feature); + } + + /** + * Propagate get feature completed via the callback. + * @param success if the operation was completed successfully + * @param features list of features available + * @param featureState status of the features corresponding to the previous parameter + */ + public void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) { + if (mGetFeatureCallback == null) { + return; + } + mGetFeatureCallback.onCompleted(success, features, featureState); + } + + /** + * Propagate challenge generated completed via the callback. + * @param sensorId id of the corresponding sensor + * @param userId id of the corresponding sensor + * @param challenge value of the challenge generated + */ + public void sendChallengeGenerated(int sensorId, int userId, long challenge) { + if (mGenerateChallengeCallback == null) { + return; + } + mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge); + } + + /** + * Propagate face detected completed via the callback. + * @param sensorId id of the corresponding sensor + * @param userId id of the corresponding user + * @param isStrongBiometric if the sensor is strong or not + */ + public void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) { + if (mFaceDetectionCallback == null) { + Slog.e(TAG, "sendFaceDetected, callback null"); + return; + } + mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric); + } + + /** + * Propagate remove face completed via the callback. + * @param face removed identifier + * @param remaining number of face enrollments remaining + */ + public void sendRemovedResult(Face face, int remaining) { + if (mRemovalCallback == null) { + return; + } + mRemovalCallback.onRemovalSucceeded(face, remaining); + } + + /** + * Propagate errors via the callback. + * @param context corresponding context + * @param errMsgId represents the framework error id + * @param vendorCode represents the vendor error code + */ + public void sendErrorResult(Context context, int errMsgId, int vendorCode) { + // emulate HAL 2.1 behavior and send real errMsgId + final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR + ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId; + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentError(clientErrMsgId, + getErrorString(context, errMsgId, vendorCode)); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationError(clientErrMsgId, + getErrorString(context, errMsgId, vendorCode)); + } else if (mRemovalCallback != null) { + mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId, + getErrorString(context, errMsgId, vendorCode)); + } else if (mFaceDetectionCallback != null) { + mFaceDetectionCallback.onDetectionError(errMsgId); + mFaceDetectionCallback = null; + } + } + + /** + * Propagate enroll progress via the callback. + * @param remaining number of enrollment steps remaining + */ + public void sendEnrollResult(int remaining) { + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentProgress(remaining); + } + } + + /** + * Propagate authentication succeeded via the callback. + * @param face matched identifier + * @param userId id of the corresponding user + * @param isStrongBiometric if the sensor is strong or not + */ + public void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) { + if (mAuthenticationCallback != null) { + final FaceManager.AuthenticationResult result = new FaceManager.AuthenticationResult( + mCryptoObject, face, userId, isStrongBiometric); + mAuthenticationCallback.onAuthenticationSucceeded(result); + } + } + + /** + * Propagate authentication failed via the callback. + */ + public void sendAuthenticatedFailed() { + if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationFailed(); + } + } + + /** + * Propagate acquired result via the callback. + * @param context corresponding context + * @param acquireInfo represents the framework acquired id + * @param vendorCode represents the vendor acquired code + */ + public void sendAcquiredResult(Context context, int acquireInfo, int vendorCode) { + if (mAuthenticationCallback != null) { + final FaceAuthenticationFrame frame = new FaceAuthenticationFrame( + new FaceDataFrame(acquireInfo, vendorCode)); + sendAuthenticationFrame(context, frame); + } else if (mEnrollmentCallback != null) { + final FaceEnrollFrame frame = new FaceEnrollFrame( + null /* cell */, + FaceEnrollStages.UNKNOWN, + new FaceDataFrame(acquireInfo, vendorCode)); + sendEnrollmentFrame(context, frame); + } + } + + /** + * Propagate authentication frame via the callback. + * @param context corresponding context + * @param frame authentication frame to be sent + */ + public void sendAuthenticationFrame(@NonNull Context context, + @Nullable FaceAuthenticationFrame frame) { + if (frame == null) { + Slog.w(TAG, "Received null authentication frame"); + } else if (mAuthenticationCallback != null) { + // TODO(b/178414967): Send additional frame data to callback + final int acquireInfo = frame.getData().getAcquiredInfo(); + final int vendorCode = frame.getData().getVendorCode(); + final int helpCode = getHelpCode(acquireInfo, vendorCode); + final String helpMessage = getAuthHelpMessage(context, acquireInfo, vendorCode); + mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); + + // Ensure that only non-null help messages are sent. + if (helpMessage != null) { + mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage); + } + } + } + + /** + * Propagate enrollment via the callback. + * @param context corresponding context + * @param frame enrollment frame to be sent + */ + public void sendEnrollmentFrame(Context context, @Nullable FaceEnrollFrame frame) { + if (frame == null) { + Slog.w(TAG, "Received null enrollment frame"); + } else if (mEnrollmentCallback != null) { + final FaceDataFrame data = frame.getData(); + final int acquireInfo = data.getAcquiredInfo(); + final int vendorCode = data.getVendorCode(); + final int helpCode = getHelpCode(acquireInfo, vendorCode); + final String helpMessage = getEnrollHelpMessage(context, acquireInfo, vendorCode); + mEnrollmentCallback.onEnrollmentFrame( + helpCode, + helpMessage, + frame.getCell(), + frame.getStage(), + data.getPan(), + data.getTilt(), + data.getDistance()); + } + } + + private static int getHelpCode(int acquireInfo, int vendorCode) { + return acquireInfo == FACE_ACQUIRED_VENDOR + ? vendorCode + FACE_ACQUIRED_VENDOR_BASE + : acquireInfo; + } +} diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 210ce2b78fca..2592630c80d2 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -37,9 +37,9 @@ import android.os.Binder; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IRemoteCallback; -import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.Trace; @@ -49,7 +49,6 @@ import android.util.Slog; import android.view.Surface; import com.android.internal.R; -import com.android.internal.os.SomeArgs; import java.util.ArrayList; import java.util.List; @@ -63,71 +62,56 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan private static final String TAG = "FaceManager"; - private static final int MSG_ENROLL_RESULT = 100; - private static final int MSG_ACQUIRED = 101; - private static final int MSG_AUTHENTICATION_SUCCEEDED = 102; - private static final int MSG_AUTHENTICATION_FAILED = 103; - private static final int MSG_ERROR = 104; - private static final int MSG_REMOVED = 105; - private static final int MSG_GET_FEATURE_COMPLETED = 106; - private static final int MSG_SET_FEATURE_COMPLETED = 107; - private static final int MSG_CHALLENGE_GENERATED = 108; - private static final int MSG_FACE_DETECTED = 109; - private static final int MSG_AUTHENTICATION_FRAME = 112; - private static final int MSG_ENROLLMENT_FRAME = 113; - private final IFaceService mService; private final Context mContext; private final IBinder mToken = new Binder(); - @Nullable private AuthenticationCallback mAuthenticationCallback; - @Nullable private FaceDetectionCallback mFaceDetectionCallback; - @Nullable private EnrollmentCallback mEnrollmentCallback; - @Nullable private RemovalCallback mRemovalCallback; - @Nullable private SetFeatureCallback mSetFeatureCallback; - @Nullable private GetFeatureCallback mGetFeatureCallback; - @Nullable private GenerateChallengeCallback mGenerateChallengeCallback; - private CryptoObject mCryptoObject; - private Face mRemovalFace; private Handler mHandler; private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>(); + private HandlerExecutor mExecutor; + + private class FaceServiceReceiver extends IFaceServiceReceiver.Stub { + private final FaceCallback mFaceCallback; - private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() { + FaceServiceReceiver(FaceCallback faceCallback) { + mFaceCallback = faceCallback; + } @Override // binder call public void onEnrollResult(Face face, int remaining) { - mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendEnrollResult(remaining)); } @Override // binder call public void onAcquired(int acquireInfo, int vendorCode) { - mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendAcquiredResult(mContext, acquireInfo, + vendorCode)); } @Override // binder call public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) { - mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, - isStrongBiometric ? 1 : 0, face).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendAuthenticatedSucceeded(face, userId, + isStrongBiometric)); } @Override // binder call public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) { - mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric) - .sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendFaceDetected(sensorId, userId, + isStrongBiometric)); } @Override // binder call public void onAuthenticationFailed() { - mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); + mExecutor.execute(mFaceCallback::sendAuthenticatedFailed); } @Override // binder call public void onError(int error, int vendorCode) { - mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendErrorResult(mContext, error, vendorCode)); } @Override // binder call public void onRemoved(Face face, int remaining) { - mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendRemovedResult(face, remaining)); if (remaining == 0) { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, @@ -137,34 +121,31 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan @Override public void onFeatureSet(boolean success, int feature) { - mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendSetFeatureCompleted(success, feature)); } @Override public void onFeatureGet(boolean success, int[] features, boolean[] featureState) { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = success; - args.arg2 = features; - args.arg3 = featureState; - mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendGetFeatureCompleted(success, features, + featureState)); } @Override public void onChallengeGenerated(int sensorId, int userId, long challenge) { - mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge) - .sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendChallengeGenerated(sensorId, userId, + challenge)); } @Override public void onAuthenticationFrame(FaceAuthenticationFrame frame) { - mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendAuthenticationFrame(mContext, frame)); } @Override public void onEnrollmentFrame(FaceEnrollFrame frame) { - mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget(); + mExecutor.execute(() -> mFaceCallback.sendEnrollmentFrame(mContext, frame)); } - }; + } /** * @hide @@ -175,7 +156,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService == null) { Slog.v(TAG, "FaceAuthenticationManagerService was null"); } - mHandler = new MyHandler(context); + mHandler = context.getMainThreadHandler(); + mExecutor = new HandlerExecutor(mHandler); if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL) == PackageManager.PERMISSION_GRANTED) { addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() { @@ -193,9 +175,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan */ private void useHandler(Handler handler) { if (handler != null) { - mHandler = new MyHandler(handler.getLooper()); - } else if (mHandler.getLooper() != mContext.getMainLooper()) { - mHandler = new MyHandler(mContext.getMainLooper()); + mHandler = handler; + mExecutor = new HandlerExecutor(mHandler); + } else if (mHandler != mContext.getMainThreadHandler()) { + mHandler = mContext.getMainThreadHandler(); + mExecutor = new HandlerExecutor(mHandler); } } @@ -249,13 +233,12 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService != null) { try { + final FaceCallback faceCallback = new FaceCallback(callback, crypto); useHandler(handler); - mAuthenticationCallback = callback; - mCryptoObject = crypto; final long operationId = crypto != null ? crypto.getOpId() : 0; Trace.beginSection("FaceManager#authenticate"); final long authId = mService.authenticate( - mToken, operationId, mServiceReceiver, options); + mToken, operationId, new FaceServiceReceiver(faceCallback), options); if (cancel != null) { cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); } @@ -292,10 +275,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan options.setOpPackageName(mContext.getOpPackageName()); options.setAttributionTag(mContext.getAttributionTag()); - mFaceDetectionCallback = callback; + final FaceCallback faceCallback = new FaceCallback(callback); try { - final long authId = mService.detectFace(mToken, mServiceReceiver, options); + final long authId = mService.detectFace(mToken, + new FaceServiceReceiver(faceCallback), options); cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId)); } catch (RemoteException e) { Slog.w(TAG, "Remote exception when requesting finger detect", e); @@ -367,11 +351,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService != null) { try { - mEnrollmentCallback = callback; + final FaceCallback faceCallback = new FaceCallback(callback); Trace.beginSection("FaceManager#enroll"); final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken, - mServiceReceiver, mContext.getOpPackageName(), disabledFeatures, - previewSurface, debugConsent, options); + new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(), + disabledFeatures, previewSurface, debugConsent, options); if (cancel != null) { cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); } @@ -419,10 +403,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService != null) { try { - mEnrollmentCallback = callback; + final FaceCallback faceCallback = new FaceCallback(callback); Trace.beginSection("FaceManager#enrollRemotely"); final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken, - mServiceReceiver, mContext.getOpPackageName(), disabledFeatures); + new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(), + disabledFeatures); if (cancel != null) { cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId)); } @@ -455,9 +440,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) { if (mService != null) { try { - mGenerateChallengeCallback = callback; - mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver, - mContext.getOpPackageName()); + final FaceCallback faceCallback = new FaceCallback(callback); + mService.generateChallenge(mToken, sensorId, userId, + new FaceServiceReceiver(faceCallback), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -528,9 +513,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan SetFeatureCallback callback) { if (mService != null) { try { - mSetFeatureCallback = callback; + final FaceCallback faceCallback = new FaceCallback(callback); mService.setFeature(mToken, userId, feature, enabled, hardwareAuthToken, - mServiceReceiver, mContext.getOpPackageName()); + new FaceServiceReceiver(faceCallback), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -544,8 +529,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan public void getFeature(int userId, int feature, GetFeatureCallback callback) { if (mService != null) { try { - mGetFeatureCallback = callback; - mService.getFeature(mToken, userId, feature, mServiceReceiver, + final FaceCallback faceCallback = new FaceCallback(callback); + mService.getFeature(mToken, userId, feature, new FaceServiceReceiver(faceCallback), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -566,10 +551,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan public void remove(Face face, int userId, RemovalCallback callback) { if (mService != null) { try { - mRemovalCallback = callback; - mRemovalFace = face; - mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver, - mContext.getOpPackageName()); + final FaceCallback faceCallback = new FaceCallback(callback, face); + mService.remove(mToken, face.getBiometricId(), userId, + new FaceServiceReceiver(faceCallback), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -584,8 +568,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan public void removeAll(int userId, @NonNull RemovalCallback callback) { if (mService != null) { try { - mRemovalCallback = callback; - mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName()); + final FaceCallback faceCallback = new FaceCallback(callback); + mService.removeAll(mToken, userId, new FaceServiceReceiver(faceCallback), + mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1270,203 +1255,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } } - private class MyHandler extends Handler { - private MyHandler(Context context) { - super(context.getMainLooper()); - } - - private MyHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(android.os.Message msg) { - Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what)); - switch (msg.what) { - case MSG_ENROLL_RESULT: - sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */); - break; - case MSG_ACQUIRED: - sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */); - break; - case MSG_AUTHENTICATION_SUCCEEDED: - sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */, - msg.arg2 == 1 /* isStrongBiometric */); - break; - case MSG_AUTHENTICATION_FAILED: - sendAuthenticatedFailed(); - break; - case MSG_ERROR: - sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */); - break; - case MSG_REMOVED: - sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */); - break; - case MSG_SET_FEATURE_COMPLETED: - sendSetFeatureCompleted((boolean) msg.obj /* success */, - msg.arg1 /* feature */); - break; - case MSG_GET_FEATURE_COMPLETED: - SomeArgs args = (SomeArgs) msg.obj; - sendGetFeatureCompleted((boolean) args.arg1 /* success */, - (int[]) args.arg2 /* features */, - (boolean[]) args.arg3 /* featureState */); - args.recycle(); - break; - case MSG_CHALLENGE_GENERATED: - sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */, - (long) msg.obj /* challenge */); - break; - case MSG_FACE_DETECTED: - sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */, - (boolean) msg.obj /* isStrongBiometric */); - break; - case MSG_AUTHENTICATION_FRAME: - sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */); - break; - case MSG_ENROLLMENT_FRAME: - sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */); - break; - default: - Slog.w(TAG, "Unknown message: " + msg.what); - } - Trace.endSection(); - } - } - - private void sendSetFeatureCompleted(boolean success, int feature) { - if (mSetFeatureCallback == null) { - return; - } - mSetFeatureCallback.onCompleted(success, feature); - } - - private void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) { - if (mGetFeatureCallback == null) { - return; - } - mGetFeatureCallback.onCompleted(success, features, featureState); - } - - private void sendChallengeGenerated(int sensorId, int userId, long challenge) { - if (mGenerateChallengeCallback == null) { - return; - } - mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge); - } - - private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) { - if (mFaceDetectionCallback == null) { - Slog.e(TAG, "sendFaceDetected, callback null"); - return; - } - mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric); - } - - private void sendRemovedResult(Face face, int remaining) { - if (mRemovalCallback == null) { - return; - } - mRemovalCallback.onRemovalSucceeded(face, remaining); - } - - private void sendErrorResult(int errMsgId, int vendorCode) { - // emulate HAL 2.1 behavior and send real errMsgId - final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR - ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId; - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentError(clientErrMsgId, - getErrorString(mContext, errMsgId, vendorCode)); - } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationError(clientErrMsgId, - getErrorString(mContext, errMsgId, vendorCode)); - } else if (mRemovalCallback != null) { - mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId, - getErrorString(mContext, errMsgId, vendorCode)); - } else if (mFaceDetectionCallback != null) { - mFaceDetectionCallback.onDetectionError(errMsgId); - mFaceDetectionCallback = null; - } - } - - private void sendEnrollResult(Face face, int remaining) { - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentProgress(remaining); - } - } - - private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) { - if (mAuthenticationCallback != null) { - final AuthenticationResult result = - new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric); - mAuthenticationCallback.onAuthenticationSucceeded(result); - } - } - - private void sendAuthenticatedFailed() { - if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationFailed(); - } - } - - private void sendAcquiredResult(int acquireInfo, int vendorCode) { - if (mAuthenticationCallback != null) { - final FaceAuthenticationFrame frame = new FaceAuthenticationFrame( - new FaceDataFrame(acquireInfo, vendorCode)); - sendAuthenticationFrame(frame); - } else if (mEnrollmentCallback != null) { - final FaceEnrollFrame frame = new FaceEnrollFrame( - null /* cell */, - FaceEnrollStages.UNKNOWN, - new FaceDataFrame(acquireInfo, vendorCode)); - sendEnrollmentFrame(frame); - } - } - - private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) { - if (frame == null) { - Slog.w(TAG, "Received null authentication frame"); - } else if (mAuthenticationCallback != null) { - // TODO(b/178414967): Send additional frame data to callback - final int acquireInfo = frame.getData().getAcquiredInfo(); - final int vendorCode = frame.getData().getVendorCode(); - final int helpCode = getHelpCode(acquireInfo, vendorCode); - final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode); - mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); - - // Ensure that only non-null help messages are sent. - if (helpMessage != null) { - mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage); - } - } - } - - private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) { - if (frame == null) { - Slog.w(TAG, "Received null enrollment frame"); - } else if (mEnrollmentCallback != null) { - final FaceDataFrame data = frame.getData(); - final int acquireInfo = data.getAcquiredInfo(); - final int vendorCode = data.getVendorCode(); - final int helpCode = getHelpCode(acquireInfo, vendorCode); - final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode); - mEnrollmentCallback.onEnrollmentFrame( - helpCode, - helpMessage, - frame.getCell(), - frame.getStage(), - data.getPan(), - data.getTilt(), - data.getDistance()); - } - } - - private static int getHelpCode(int acquireInfo, int vendorCode) { - return acquireInfo == FACE_ACQUIRED_VENDOR - ? vendorCode + FACE_ACQUIRED_VENDOR_BASE - : acquireInfo; - } - /** * @hide */ diff --git a/core/java/android/hardware/fingerprint/FingerprintCallback.java b/core/java/android/hardware/fingerprint/FingerprintCallback.java new file mode 100644 index 000000000000..e4fbe6e09709 --- /dev/null +++ b/core/java/android/hardware/fingerprint/FingerprintCallback.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2024 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 android.hardware.fingerprint; + +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR_BASE; +import static android.hardware.fingerprint.FingerprintManager.getAcquiredString; +import static android.hardware.fingerprint.FingerprintManager.getErrorString; + +import android.annotation.IntDef; +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; +import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; +import android.hardware.fingerprint.FingerprintManager.CryptoObject; +import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; +import android.hardware.fingerprint.FingerprintManager.FingerprintDetectionCallback; +import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback; +import android.hardware.fingerprint.FingerprintManager.RemovalCallback; +import android.util.Slog; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Encapsulates callbacks and client specific information for each fingerprint related request. + * @hide + */ +public class FingerprintCallback { + private static final String TAG = "FingerprintCallback"; + public static final int REMOVE_SINGLE = 1; + public static final int REMOVE_ALL = 2; + @IntDef({REMOVE_SINGLE, REMOVE_ALL}) + public @interface RemoveRequest {} + @Nullable + private AuthenticationCallback mAuthenticationCallback; + @Nullable + private EnrollmentCallback mEnrollmentCallback; + @Nullable + private RemovalCallback mRemovalCallback; + @Nullable + private GenerateChallengeCallback mGenerateChallengeCallback; + @Nullable + private FingerprintDetectionCallback mFingerprintDetectionCallback; + @Nullable + private CryptoObject mCryptoObject; + @Nullable + private @RemoveRequest int mRemoveRequest; + @Nullable + private Fingerprint mRemoveFingerprint; + + /** + * Construction for fingerprint authentication client callback. + */ + FingerprintCallback(@NonNull AuthenticationCallback authenticationCallback, + @Nullable CryptoObject cryptoObject) { + mAuthenticationCallback = authenticationCallback; + mCryptoObject = cryptoObject; + } + + /** + * Construction for fingerprint detect client callback. + */ + FingerprintCallback(@NonNull FingerprintDetectionCallback fingerprintDetectionCallback) { + mFingerprintDetectionCallback = fingerprintDetectionCallback; + } + + /** + * Construction for fingerprint enroll client callback. + */ + FingerprintCallback(@NonNull EnrollmentCallback enrollmentCallback) { + mEnrollmentCallback = enrollmentCallback; + } + + /** + * Construction for fingerprint generate challenge client callback. + */ + FingerprintCallback(@NonNull GenerateChallengeCallback generateChallengeCallback) { + mGenerateChallengeCallback = generateChallengeCallback; + } + + /** + * Construction for fingerprint removal client callback. + */ + FingerprintCallback(@NonNull RemovalCallback removalCallback, @RemoveRequest int removeRequest, + @Nullable Fingerprint removeFingerprint) { + mRemovalCallback = removalCallback; + mRemoveRequest = removeRequest; + mRemoveFingerprint = removeFingerprint; + } + + /** + * Propagate enroll progress via the callback. + * @param remaining number of enrollment steps remaining + */ + public void sendEnrollResult(int remaining) { + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentProgress(remaining); + } + } + + /** + * Propagate remove face completed via the callback. + * @param fingerprint removed identifier + * @param remaining number of face enrollments remaining + */ + public void sendRemovedResult(@Nullable Fingerprint fingerprint, int remaining) { + if (mRemovalCallback == null) { + return; + } + + if (mRemoveRequest == REMOVE_SINGLE) { + if (fingerprint == null) { + Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null"); + return; + } + + if (mRemoveFingerprint == null) { + Slog.e(TAG, "Missing fingerprint"); + return; + } + + final int fingerId = fingerprint.getBiometricId(); + int reqFingerId = mRemoveFingerprint.getBiometricId(); + if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) { + Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId); + return; + } + } + + mRemovalCallback.onRemovalSucceeded(fingerprint, remaining); + } + + /** + * Propagate authentication succeeded via the callback. + * @param fingerprint matched identifier + * @param userId id of the corresponding user + * @param isStrongBiometric if the sensor is strong or not + */ + public void sendAuthenticatedSucceeded(@NonNull Fingerprint fingerprint, int userId, + boolean isStrongBiometric) { + if (mAuthenticationCallback == null) { + Slog.e(TAG, "Authentication succeeded but callback is null."); + return; + } + + final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fingerprint, + userId, isStrongBiometric); + mAuthenticationCallback.onAuthenticationSucceeded(result); + } + + /** + * Propagate authentication failed via the callback. + */ + public void sendAuthenticatedFailed() { + if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationFailed(); + } + } + + /** + * Propagate acquired result via the callback. + * @param context corresponding context + * @param acquireInfo represents the framework acquired id + * @param vendorCode represents the vendor acquired code + */ + public void sendAcquiredResult(@NonNull Context context, int acquireInfo, int vendorCode) { + if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); + } + if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) { + mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD); + } + final String msg = getAcquiredString(context, acquireInfo, vendorCode); + if (msg == null) { + return; + } + // emulate HAL 2.1 behavior and send real acquiredInfo + final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR + ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo; + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); + } else if (mAuthenticationCallback != null) { + if (acquireInfo != FINGERPRINT_ACQUIRED_START) { + mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); + } + } + } + + /** + * Propagate errors via the callback. + * @param context corresponding context + * @param errMsgId represents the framework error id + * @param vendorCode represents the vendor error code + */ + public void sendErrorResult(@NonNull Context context, int errMsgId, int vendorCode) { + // emulate HAL 2.1 behavior and send real errMsgId + final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR + ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId; + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentError(clientErrMsgId, + getErrorString(context, errMsgId, vendorCode)); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationError(clientErrMsgId, + getErrorString(context, errMsgId, vendorCode)); + } else if (mRemovalCallback != null) { + mRemovalCallback.onRemovalError(mRemoveFingerprint, clientErrMsgId, + getErrorString(context, errMsgId, vendorCode)); + } else if (mFingerprintDetectionCallback != null) { + mFingerprintDetectionCallback.onDetectionError(errMsgId); + mFingerprintDetectionCallback = null; + } + } + + /** + * Propagate challenge generated completed via the callback. + * @param sensorId id of the corresponding sensor + * @param userId id of the corresponding sensor + * @param challenge value of the challenge generated + */ + public void sendChallengeGenerated(long challenge, int sensorId, int userId) { + if (mGenerateChallengeCallback == null) { + Slog.e(TAG, "sendChallengeGenerated, callback null"); + return; + } + mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge); + } + + /** + * Propagate fingerprint detected completed via the callback. + * @param sensorId id of the corresponding sensor + * @param userId id of the corresponding user + * @param isStrongBiometric if the sensor is strong or not + */ + public void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) { + if (mFingerprintDetectionCallback == null) { + Slog.e(TAG, "sendFingerprintDetected, callback null"); + return; + } + mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric); + } + + /** + * Propagate udfps pointer down via the callback. + * @param sensorId id of the corresponding sensor + */ + public void sendUdfpsPointerDown(int sensorId) { + if (mAuthenticationCallback == null) { + Slog.e(TAG, "sendUdfpsPointerDown, callback null"); + } else { + mAuthenticationCallback.onUdfpsPointerDown(sensorId); + } + + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onUdfpsPointerDown(sensorId); + } + } + + /** + * Propagate udfps pointer up via the callback. + * @param sensorId id of the corresponding sensor + */ + public void sendUdfpsPointerUp(int sensorId) { + if (mAuthenticationCallback == null) { + Slog.e(TAG, "sendUdfpsPointerUp, callback null"); + } else { + mAuthenticationCallback.onUdfpsPointerUp(sensorId); + } + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onUdfpsPointerUp(sensorId); + } + } + + /** + * Propagate udfps overlay shown via the callback. + */ + public void sendUdfpsOverlayShown() { + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onUdfpsOverlayShown(); + } + } +} diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 81e321d96aa6..37f2fb2a538a 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -25,6 +25,8 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE; import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT; +import static android.hardware.fingerprint.FingerprintCallback.REMOVE_ALL; +import static android.hardware.fingerprint.FingerprintCallback.REMOVE_SINGLE; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE; @@ -57,9 +59,9 @@ import android.os.Build; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IRemoteCallback; -import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; @@ -94,19 +96,6 @@ import javax.crypto.Mac; @RequiresFeature(PackageManager.FEATURE_FINGERPRINT) public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants { private static final String TAG = "FingerprintManager"; - private static final boolean DEBUG = true; - private static final int MSG_ENROLL_RESULT = 100; - private static final int MSG_ACQUIRED = 101; - private static final int MSG_AUTHENTICATION_SUCCEEDED = 102; - private static final int MSG_AUTHENTICATION_FAILED = 103; - private static final int MSG_ERROR = 104; - private static final int MSG_REMOVED = 105; - private static final int MSG_CHALLENGE_GENERATED = 106; - private static final int MSG_FINGERPRINT_DETECTED = 107; - private static final int MSG_UDFPS_POINTER_DOWN = 108; - private static final int MSG_UDFPS_POINTER_UP = 109; - private static final int MSG_POWER_BUTTON_PRESSED = 110; - private static final int MSG_UDFPS_OVERLAY_SHOWN = 111; /** * @hide @@ -148,34 +137,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ public static final int SENSOR_ID_ANY = -1; - private static class RemoveTracker { - static final int REMOVE_SINGLE = 1; - static final int REMOVE_ALL = 2; - @IntDef({REMOVE_SINGLE, REMOVE_ALL}) - @interface RemoveRequest {} + private final IFingerprintService mService; + private final Context mContext; + private final IBinder mToken = new Binder(); - final @RemoveRequest int mRemoveRequest; - @Nullable final Fingerprint mSingleFingerprint; - - RemoveTracker(@RemoveRequest int request, @Nullable Fingerprint fingerprint) { - mRemoveRequest = request; - mSingleFingerprint = fingerprint; - } - } - - private IFingerprintService mService; - private Context mContext; - private IBinder mToken = new Binder(); - private AuthenticationCallback mAuthenticationCallback; - private FingerprintDetectionCallback mFingerprintDetectionCallback; - private EnrollmentCallback mEnrollmentCallback; - private RemovalCallback mRemovalCallback; - private GenerateChallengeCallback mGenerateChallengeCallback; - private CryptoObject mCryptoObject; - @Nullable private RemoveTracker mRemoveTracker; private Handler mHandler; @Nullable private float[] mEnrollStageThresholds; private List<FingerprintSensorPropertiesInternal> mProps = new ArrayList<>(); + private HandlerExecutor mExecutor; /** * Retrieves a list of properties for all fingerprint sensors on the device. @@ -395,7 +364,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} */ @Deprecated - public static abstract class AuthenticationCallback + public abstract static class AuthenticationCallback extends BiometricAuthenticator.AuthenticationCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. @@ -479,7 +448,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * * @hide */ - public static abstract class EnrollmentCallback { + public abstract static class EnrollmentCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. * No further callbacks will be made on this object. @@ -536,7 +505,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * * @hide */ - public static abstract class RemovalCallback { + public abstract static class RemovalCallback { /** * Called when the given fingerprint can't be removed. * @param fp The fingerprint that the call attempted to remove @@ -559,7 +528,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * @hide */ - public static abstract class LockoutResetCallback { + public abstract static class LockoutResetCallback { /** * Called when lockout period expired and clients are allowed to listen for fingerprint @@ -584,9 +553,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ private void useHandler(Handler handler) { if (handler != null) { - mHandler = new MyHandler(handler.getLooper()); - } else if (mHandler.getLooper() != mContext.getMainLooper()) { - mHandler = new MyHandler(mContext.getMainLooper()); + mHandler = handler; + mExecutor = new HandlerExecutor(mHandler); + } else if (mHandler != mContext.getMainThreadHandler()) { + mHandler = mContext.getMainThreadHandler(); + mExecutor = new HandlerExecutor(mHandler); } } @@ -676,11 +647,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing if (mService != null) { try { + final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback, + crypto); useHandler(handler); - mAuthenticationCallback = callback; - mCryptoObject = crypto; final long operationId = crypto != null ? crypto.getOpId() : 0; - final long authId = mService.authenticate(mToken, operationId, mServiceReceiver, options); + final long authId = mService.authenticate(mToken, operationId, + new FingerprintServiceReceiver(fingerprintCallback), options); if (cancel != null) { cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); } @@ -715,10 +687,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing options.setOpPackageName(mContext.getOpPackageName()); options.setAttributionTag(mContext.getAttributionTag()); - mFingerprintDetectionCallback = callback; + final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback); try { - final long authId = mService.detectFingerprint(mToken, mServiceReceiver, options); + final long authId = mService.detectFingerprint(mToken, + new FingerprintServiceReceiver(fingerprintCallback), options); cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId)); } catch (RemoteException e) { Slog.w(TAG, "Remote exception when requesting finger detect", e); @@ -767,9 +740,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing if (mService != null) { try { - mEnrollmentCallback = callback; + final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback); final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId, - mServiceReceiver, mContext.getOpPackageName(), enrollReason, options); + new FingerprintServiceReceiver(fingerprintCallback), + mContext.getOpPackageName(), enrollReason, options); if (cancel != null) { cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); } @@ -799,12 +773,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(MANAGE_FINGERPRINT) public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) { if (mService != null) try { - mGenerateChallengeCallback = callback; - mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver, - mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback); + mService.generateChallenge(mToken, sensorId, userId, + new FingerprintServiceReceiver(fingerprintCallback), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -875,13 +850,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(MANAGE_FINGERPRINT) public void remove(Fingerprint fp, int userId, RemovalCallback callback) { if (mService != null) try { - mRemovalCallback = callback; - mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_SINGLE, fp); - mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver, - mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback, + REMOVE_SINGLE, fp); + mService.remove(mToken, fp.getBiometricId(), userId, + new FingerprintServiceReceiver(fingerprintCallback), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -892,9 +868,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public void removeAll(int userId, @NonNull RemovalCallback callback) { if (mService != null) { try { - mRemovalCallback = callback; - mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_ALL, null /* fp */); - mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName()); + final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback, + REMOVE_ALL, null); + mService.removeAll(mToken, userId, + new FingerprintServiceReceiver(fingerprintCallback), + mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1162,7 +1140,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPowerPressed() { Slog.i(TAG, "onPowerPressed"); - mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget(); + mExecutor.execute(() -> sendPowerPressed()); } /** @@ -1346,199 +1324,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - private class MyHandler extends Handler { - private MyHandler(Context context) { - super(context.getMainLooper()); - } - - private MyHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case MSG_ENROLL_RESULT: - sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */); - break; - case MSG_ACQUIRED: - sendAcquiredResult(msg.arg1 /* acquire info */, - msg.arg2 /* vendorCode */); - break; - case MSG_AUTHENTICATION_SUCCEEDED: - sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */, - msg.arg2 == 1 /* isStrongBiometric */); - break; - case MSG_AUTHENTICATION_FAILED: - sendAuthenticatedFailed(); - break; - case MSG_ERROR: - sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */); - break; - case MSG_REMOVED: - sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */); - break; - case MSG_CHALLENGE_GENERATED: - sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */, - (long) msg.obj /* challenge */); - break; - case MSG_FINGERPRINT_DETECTED: - sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */, - (boolean) msg.obj /* isStrongBiometric */); - break; - case MSG_UDFPS_POINTER_DOWN: - sendUdfpsPointerDown(msg.arg1 /* sensorId */); - break; - case MSG_UDFPS_POINTER_UP: - sendUdfpsPointerUp(msg.arg1 /* sensorId */); - break; - case MSG_POWER_BUTTON_PRESSED: - sendPowerPressed(); - break; - case MSG_UDFPS_OVERLAY_SHOWN: - sendUdfpsOverlayShown(); - default: - Slog.w(TAG, "Unknown message: " + msg.what); - - } - } - } - - private void sendRemovedResult(Fingerprint fingerprint, int remaining) { - if (mRemovalCallback == null) { - return; - } - - if (mRemoveTracker == null) { - Slog.w(TAG, "Removal tracker is null"); - return; - } - - if (mRemoveTracker.mRemoveRequest == RemoveTracker.REMOVE_SINGLE) { - if (fingerprint == null) { - Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null"); - return; - } - - if (mRemoveTracker.mSingleFingerprint == null) { - Slog.e(TAG, "Missing fingerprint"); - return; - } - - final int fingerId = fingerprint.getBiometricId(); - int reqFingerId = mRemoveTracker.mSingleFingerprint.getBiometricId(); - if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) { - Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId); - return; - } - } - - mRemovalCallback.onRemovalSucceeded(fingerprint, remaining); - } - - private void sendEnrollResult(Fingerprint fp, int remaining) { - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentProgress(remaining); - } - } - - private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) { - if (mAuthenticationCallback != null) { - final AuthenticationResult result = - new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric); - mAuthenticationCallback.onAuthenticationSucceeded(result); - } - } - - private void sendAuthenticatedFailed() { - if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationFailed(); - } - } - - private void sendAcquiredResult(int acquireInfo, int vendorCode) { - if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); - } - if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) { - mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD); - } - final String msg = getAcquiredString(mContext, acquireInfo, vendorCode); - if (msg == null) { - return; - } - // emulate HAL 2.1 behavior and send real acquiredInfo - final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR - ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo; - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); - } else if (mAuthenticationCallback != null) { - if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) { - mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); - } - } - } - - private void sendErrorResult(int errMsgId, int vendorCode) { - // emulate HAL 2.1 behavior and send real errMsgId - final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR - ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId; - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentError(clientErrMsgId, - getErrorString(mContext, errMsgId, vendorCode)); - } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationError(clientErrMsgId, - getErrorString(mContext, errMsgId, vendorCode)); - } else if (mRemovalCallback != null) { - final Fingerprint fp = mRemoveTracker != null - ? mRemoveTracker.mSingleFingerprint : null; - mRemovalCallback.onRemovalError(fp, clientErrMsgId, - getErrorString(mContext, errMsgId, vendorCode)); - } else if (mFingerprintDetectionCallback != null) { - mFingerprintDetectionCallback.onDetectionError(errMsgId); - mFingerprintDetectionCallback = null; - } - } - - private void sendChallengeGenerated(int sensorId, int userId, long challenge) { - if (mGenerateChallengeCallback == null) { - Slog.e(TAG, "sendChallengeGenerated, callback null"); - return; - } - mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge); - } - - private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) { - if (mFingerprintDetectionCallback == null) { - Slog.e(TAG, "sendFingerprintDetected, callback null"); - return; - } - mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric); - } - - private void sendUdfpsPointerDown(int sensorId) { - if (mAuthenticationCallback == null) { - Slog.e(TAG, "sendUdfpsPointerDown, callback null"); - } else { - mAuthenticationCallback.onUdfpsPointerDown(sensorId); - } - - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onUdfpsPointerDown(sensorId); - } - } - - private void sendUdfpsPointerUp(int sensorId) { - if (mAuthenticationCallback == null) { - Slog.e(TAG, "sendUdfpsPointerUp, callback null"); - } else { - mAuthenticationCallback.onUdfpsPointerUp(sensorId); - } - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onUdfpsPointerUp(sensorId); - } - } - private void sendPowerPressed() { try { mService.onPowerPressed(); @@ -1547,12 +1332,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - private void sendUdfpsOverlayShown() { - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onUdfpsOverlayShown(); - } - } - /** * @hide */ @@ -1562,7 +1341,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing if (mService == null) { Slog.v(TAG, "FingerprintService was null"); } - mHandler = new MyHandler(context); if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL) == PackageManager.PERMISSION_GRANTED) { addAuthenticatorsRegisteredCallback( @@ -1574,6 +1352,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } }); } + mHandler = context.getMainThreadHandler(); + mExecutor = new HandlerExecutor(mHandler); } private int getCurrentUserId() { @@ -1773,66 +1553,72 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return null; } - private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { + class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub { + private final FingerprintCallback mFingerprintCallback; + + FingerprintServiceReceiver(FingerprintCallback fingerprintCallback) { + mFingerprintCallback = fingerprintCallback; + } @Override // binder call public void onEnrollResult(Fingerprint fp, int remaining) { - mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, fp).sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendEnrollResult(remaining)); } @Override // binder call public void onAcquired(int acquireInfo, int vendorCode) { - mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendAcquiredResult(mContext, acquireInfo, + vendorCode)); } @Override // binder call public void onAuthenticationSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) { - mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0, - fp).sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendAuthenticatedSucceeded(fp, userId, + isStrongBiometric)); } @Override public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) { - mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, sensorId, userId, isStrongBiometric) - .sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendFingerprintDetected(sensorId, userId, + isStrongBiometric)); } @Override // binder call public void onAuthenticationFailed() { - mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); + mExecutor.execute(mFingerprintCallback::sendAuthenticatedFailed); } @Override // binder call public void onError(int error, int vendorCode) { - mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendErrorResult(mContext, error, + vendorCode)); } @Override // binder call public void onRemoved(Fingerprint fp, int remaining) { - mHandler.obtainMessage(MSG_REMOVED, remaining, 0, fp).sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendRemovedResult(fp, remaining)); } @Override // binder call public void onChallengeGenerated(int sensorId, int userId, long challenge) { - mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge) - .sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendChallengeGenerated(challenge, sensorId, + userId)); } @Override // binder call public void onUdfpsPointerDown(int sensorId) { - mHandler.obtainMessage(MSG_UDFPS_POINTER_DOWN, sensorId, 0).sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerDown(sensorId)); } @Override // binder call public void onUdfpsPointerUp(int sensorId) { - mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget(); + mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerUp(sensorId)); } @Override public void onUdfpsOverlayShown() { - mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget(); + mExecutor.execute(mFingerprintCallback::sendUdfpsOverlayShown); } - }; - + } } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 4ae0a5733f08..3b5990181d8b 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -98,6 +98,8 @@ interface IPermissionManager { IBinder registerAttributionSource(in AttributionSourceState source); + int getNumRegisteredAttributionSources(int uid); + boolean isRegisteredAttributionSource(in AttributionSourceState source); int checkPermission(String packageName, String permissionName, String persistentDeviceId, diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index fe3fa8cf34f5..2daf4ac8226e 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1675,6 +1675,21 @@ public final class PermissionManager { } /** + * Gets the number of currently registered attribution sources for a particular UID. This should + * only be used for testing purposes. + * @hide + */ + @RequiresPermission(Manifest.permission.UPDATE_APP_OPS_STATS) + public int getNumRegisteredAttributionSourcesForTest(int uid) { + try { + return mPermissionManager.getNumRegisteredAttributionSources(uid); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return -1; + } + + /** * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE * USED in CTS or local tests. * diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 23ece310b926..abb4917024ce 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -125,6 +125,30 @@ flag { } flag { + name: "sensitive_content_metrics_bugfix" + namespace: "permissions" + description: "Enables metrics bugfixes for sensitive content/notification features" + bug: "312784351" + # Referenced in WM where WM starts before DeviceConfig + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "sensitive_content_recents_screenshot_bugfix" + namespace: "permissions" + description: "Enables recents screenshot bugfixes for sensitive content/notification features" + bug: "312784351" + # Referenced in WM where WM starts before DeviceConfig + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "device_aware_permissions_enabled" is_fixed_read_only: true namespace: "permissions" diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl index 1a6cf881e337..ddb662ad42cb 100644 --- a/core/java/android/security/IFileIntegrityService.aidl +++ b/core/java/android/security/IFileIntegrityService.aidl @@ -28,6 +28,8 @@ interface IFileIntegrityService { boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName); IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd); + + @EnforcePermission("SETUP_FSVERITY") int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath, in String packageName); } diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index e6a84df16c27..269839b61bef 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -37,7 +37,6 @@ import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; -import android.view.autofill.IAutoFillManagerClient; import com.android.internal.os.IResultReceiver; @@ -642,7 +641,7 @@ public abstract class AutofillService extends Service { @Override public void onFillCredentialRequest(FillRequest request, IFillCallback callback, - IAutoFillManagerClient autofillClientCallback) { + IBinder autofillClientCallback) { ICancellationSignal transport = CancellationSignal.createTransport(); try { callback.onCancellable(transport); @@ -724,7 +723,7 @@ public abstract class AutofillService extends Service { */ public void onFillCredentialRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback, - @NonNull IAutoFillManagerClient autofillClientCallback) {} + @NonNull IBinder autofillClientCallback) {} /** * Called by the Android system to convert a credential manager response to a dataset diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 2c2feae7aeea..3b64b8a0ec5e 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -16,13 +16,13 @@ package android.service.autofill; +import android.os.IBinder; import android.service.autofill.ConvertCredentialRequest; import android.service.autofill.IConvertCredentialCallback; import android.service.autofill.FillRequest; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; import android.service.autofill.SaveRequest; -import android.view.autofill.IAutoFillManagerClient; import com.android.internal.os.IResultReceiver; /** @@ -34,7 +34,7 @@ oneway interface IAutoFillService { void onConnectedStateChanged(boolean connected); void onFillRequest(in FillRequest request, in IFillCallback callback); void onFillCredentialRequest(in FillRequest request, in IFillCallback callback, - in IAutoFillManagerClient client); + in IBinder client); void onSaveRequest(in SaveRequest request, in ISaveCallback callback); void onSavedPasswordCountRequest(in IResultReceiver receiver); void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 4d4cb6c09c15..815fc5820189 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -87,12 +87,6 @@ public class FeatureFlagUtils { "settings_need_connected_ble_device_for_broadcast"; /** - * Enable new language and keyboard settings UI - * @hide - */ - public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui"; - - /** * Enable new modifier key settings UI * @hide */ @@ -221,7 +215,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true"); - DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false"); @@ -249,7 +242,6 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME); - PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE); diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 3e615394f7de..f315f55c7ed4 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -48,6 +48,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityRequestPreparer; +import android.view.accessibility.Flags; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.window.ScreenCapture; @@ -1286,6 +1287,15 @@ public final class AccessibilityInteractionController { } /** + * Destroy {@link AccessibilityInteractionController} and clean up the pending actions. + */ + public void destroy() { + if (Flags.preventLeakingViewrootimpl()) { + mHandler.removeCallbacksAndMessages(null); + } + } + + /** * This class encapsulates a prefetching strategy for the accessibility APIs for * querying window content. It is responsible to prefetch a batch of * AccessibilityNodeInfos in addition to the one for a requested node. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 09f98d7e1d82..8d55777a2b8e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -89,6 +89,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; @@ -110,12 +111,12 @@ import static android.view.accessibility.Flags.fixMergedContentChangeEventV2; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle; import static android.view.flags.Flags.sensitiveContentAppProtection; +import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly; import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly; import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly; +import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly; import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; -import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly; -import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; @@ -1441,6 +1442,7 @@ public final class ViewRootImpl implements ViewParent, // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; + adjustLayoutInDisplayCutoutMode(attrs); setAccessibilityFocus(null, null); if (view instanceof RootViewSurfaceTaker) { @@ -2043,6 +2045,9 @@ public final class ViewRootImpl implements ViewParent, final int appearance = mWindowAttributes.insetsFlags.appearance; final int behavior = mWindowAttributes.insetsFlags.behavior; + // Calling this before copying prevents redundant LAYOUT_CHANGED. + final int layoutInDisplayCutoutModeFromCaller = adjustLayoutInDisplayCutoutMode(attrs); + final int changes = mWindowAttributes.copyFrom(attrs); if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) { // Recompute system ui visibility. @@ -2059,6 +2064,9 @@ public final class ViewRootImpl implements ViewParent, mWindowAttributes.packageName = mBasePackageName; } + // Restore the layoutInDisplayCutoutMode of the caller; + attrs.layoutInDisplayCutoutMode = layoutInDisplayCutoutModeFromCaller; + // Restore preserved flags. mWindowAttributes.systemUiVisibility = systemUiVisibility; mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility; @@ -2101,6 +2109,19 @@ public final class ViewRootImpl implements ViewParent, } } + private int adjustLayoutInDisplayCutoutMode(WindowManager.LayoutParams attrs) { + final int originalMode = attrs.layoutInDisplayCutoutMode; + if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0 + && attrs.isFullscreen() + && attrs.getFitInsetsTypes() == 0 + && attrs.getFitInsetsSides() == 0) { + if (originalMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { + attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + } + } + return originalMode; + } + void handleAppVisibility(boolean visible) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple( @@ -6088,6 +6109,11 @@ public final class ViewRootImpl implements ViewParent, mAccessibilityInteractionConnectionManager.ensureNoConnection(); mAccessibilityInteractionConnectionManager.ensureNoDirectConnection(); removeSendWindowContentChangedCallback(); + if (android.view.accessibility.Flags.preventLeakingViewrootimpl() + && mAccessibilityInteractionController != null) { + mAccessibilityInteractionController.destroy(); + mAccessibilityInteractionController = null; + } destroyHardwareRenderer(); diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 12ce0f47c460..bdfc236d82e0 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -27,6 +27,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresNoPermission; import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -1190,6 +1191,8 @@ public final class AccessibilityInteractionClient /** * {@inheritDoc} */ + @Override + @RequiresNoPermission public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) { synchronized (mInstanceLock) { @@ -1231,6 +1234,8 @@ public final class AccessibilityInteractionClient /** * {@inheritDoc} */ + @Override + @RequiresNoPermission public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, int interactionId) { synchronized (mInstanceLock) { @@ -1260,6 +1265,7 @@ public final class AccessibilityInteractionClient * {@inheritDoc} */ @Override + @RequiresNoPermission public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos, int interactionId) { int interactionIdWaitingForPrefetchResultCopy = -1; @@ -1324,6 +1330,8 @@ public final class AccessibilityInteractionClient /** * {@inheritDoc} */ + @Override + @RequiresNoPermission public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) { synchronized (mInstanceLock) { if (interactionId > mInteractionId) { @@ -1372,6 +1380,7 @@ public final class AccessibilityInteractionClient * @param interactionId The interaction id of the request. */ @Override + @RequiresNoPermission public void sendTakeScreenshotOfWindowError( @AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) { synchronized (mInstanceLock) { @@ -1729,6 +1738,7 @@ public final class AccessibilityInteractionClient * @param interactionId The interaction id of the request. */ @Override + @RequiresNoPermission public void sendAttachOverlayResult( @AccessibilityService.AttachOverlayResult int result, int interactionId) { if (!Flags.a11yOverlayCallbacks()) { diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index fe595193047c..a9e5db5d1873 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -34,6 +34,7 @@ oneway interface IAccessibilityInteractionConnectionCallback { * @param interactionId The interaction id to match the result with the request. */ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + @RequiresNoPermission void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId); /** @@ -43,6 +44,7 @@ oneway interface IAccessibilityInteractionConnectionCallback { * @param interactionId The interaction id to match the result with the request. */ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + @RequiresNoPermission void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos, int interactionId); @@ -52,6 +54,7 @@ oneway interface IAccessibilityInteractionConnectionCallback { * @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of. * @param infos The result {@link AccessibilityNodeInfo}s. */ + @RequiresNoPermission void setPrefetchAccessibilityNodeInfoResult( in List<AccessibilityNodeInfo> infos, int interactionId); @@ -62,15 +65,18 @@ oneway interface IAccessibilityInteractionConnectionCallback { * @param interactionId The interaction id to match the result with the request. */ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + @RequiresNoPermission void setPerformAccessibilityActionResult(boolean succeeded, int interactionId); /** * Sends an error code for a window screenshot request to the requesting client. */ + @RequiresNoPermission void sendTakeScreenshotOfWindowError(int errorCode, int interactionId); /** * Sends an result code for an attach overlay request to the requesting client. */ + @RequiresNoPermission void sendAttachOverlayResult(int result, int interactionId); } diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 334965abd8c9..c9d99d1a407e 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -142,6 +142,16 @@ flag { } flag { + name: "prevent_leaking_viewrootimpl" + namespace: "accessibility" + description: "Clear pending messages and callbacks of the handler in AccessibilityInteractionController when the ViewRootImpl is detached from Window to prevent leaking ViewRootImpl" + bug: "320701910" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "support_system_pinch_zoom_opt_out_apis" namespace: "accessibility" description: "Feature flag for declaring system pinch zoom opt-out apis" diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index a868d487b82f..1ffb4ffd12c2 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -144,17 +144,26 @@ public class WindowTokenClient extends Binder { if (context == null) { return; } - final ClientTransactionListenerController controller = - ClientTransactionListenerController.getInstance(); - controller.onContextConfigurationPreChanged(context); - try { + if (shouldReportConfigChange) { + // Only report to ClientTransactionListenerController when shouldReportConfigChange, + // which is on the MainThread. + final ClientTransactionListenerController controller = + getClientTransactionListenerController(); + controller.onContextConfigurationPreChanged(context); + try { + onConfigurationChangedInner(context, newConfig, newDisplayId, + shouldReportConfigChange); + } finally { + controller.onContextConfigurationPostChanged(context); + } + } else { onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange); - } finally { - controller.onContextConfigurationPostChanged(context); } } - private void onConfigurationChangedInner(@NonNull Context context, + /** Handles onConfiguration changed. */ + @VisibleForTesting + public void onConfigurationChangedInner(@NonNull Context context, @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig); final boolean displayChanged; @@ -233,4 +242,11 @@ public class WindowTokenClient extends Binder { mContextRef.clear(); } } + + /** Gets {@link ClientTransactionListenerController}. */ + @VisibleForTesting + @NonNull + public ClientTransactionListenerController getClientTransactionListenerController() { + return ClientTransactionListenerController.getInstance(); + } } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 18413530d575..52487fb1ecaa 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -2486,9 +2486,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); params.setFitInsetsSides(0); params.setFitInsetsTypes(0); - if (mEdgeToEdgeEnforced) { - params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - } } if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index bdb33c4b151c..cb1abf13c109 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -16,10 +16,12 @@ package com.android.internal.view.menu; +import android.app.AppGlobals; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.text.TextFlags; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -59,6 +61,8 @@ public class ListMenuItemView extends LinearLayout private int mMenuType; + private boolean mUseNewContextMenu; + private LayoutInflater mInflater; private boolean mForceShowIcon; @@ -85,6 +89,10 @@ public class ListMenuItemView extends LinearLayout a.recycle(); b.recycle(); + + mUseNewContextMenu = AppGlobals.getIntCoreSetting( + TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, + TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0; } public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { @@ -281,7 +289,9 @@ public class ListMenuItemView extends LinearLayout private void insertIconView() { LayoutInflater inflater = getInflater(); - mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon, + mIconView = (ImageView) inflater.inflate( + mUseNewContextMenu ? com.android.internal.R.layout.list_menu_item_fixed_size_icon : + com.android.internal.R.layout.list_menu_item_icon, this, false); addContentView(mIconView, 0); } diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java index 36828f2dadca..1979e4fe7a90 100644 --- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java +++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java @@ -16,26 +16,24 @@ package com.android.internal.view.menu; -import android.app.AppGlobals; import android.content.Context; import android.content.res.Resources; import android.os.Parcelable; -import android.text.TextFlags; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnKeyListener; -import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.widget.AdapterView.OnItemClickListener; +import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.ListView; import android.widget.MenuPopupWindow; import android.widget.PopupWindow; -import android.widget.PopupWindow.OnDismissListener; import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.PopupWindow.OnDismissListener; import java.util.Objects; @@ -46,8 +44,6 @@ import java.util.Objects; final class StandardMenuPopup extends MenuPopup implements OnDismissListener, OnItemClickListener, MenuPresenter, OnKeyListener { private static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout; - private static final int ITEM_LAYOUT_MATERIAL = - com.android.internal.R.layout.popup_menu_item_layout_material; private final Context mContext; @@ -57,7 +53,6 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On private final int mPopupMaxWidth; private final int mPopupStyleAttr; private final int mPopupStyleRes; - // The popup window is final in order to couple its lifecycle to the lifecycle of the // StandardMenuPopup. private final MenuPopupWindow mPopup; @@ -119,15 +114,10 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr, int popupStyleRes, boolean overflowOnly) { mContext = Objects.requireNonNull(context); - boolean useNewContextMenu = AppGlobals.getIntCoreSetting( - TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, - TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0; - mMenu = menu; mOverflowOnly = overflowOnly; final LayoutInflater inflater = LayoutInflater.from(context); - mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, - useNewContextMenu ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT); + mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT); mPopupStyleAttr = popupStyleAttr; mPopupStyleRes = popupStyleRes; diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml index d8514608e8dd..a30be6a13db6 100644 --- a/core/res/res/layout/list_menu_item_icon.xml +++ b/core/res/res/layout/list_menu_item_icon.xml @@ -20,7 +20,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="8dip" - android:layout_marginEnd="8dip" + android:layout_marginEnd="-8dip" android:layout_marginTop="8dip" android:layout_marginBottom="8dip" android:scaleType="centerInside" diff --git a/core/res/res/layout/popup_menu_item_layout_material.xml b/core/res/res/layout/popup_menu_item_layout_material.xml deleted file mode 100644 index e20ead62032c..000000000000 --- a/core/res/res/layout/popup_menu_item_layout_material.xml +++ /dev/null @@ -1,91 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2024 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. - - Forked from the popup_menu_item_layout.xml for material support. When you edit this file, you - may also need to update that file. ---> - -<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minWidth="196dip" - android:orientation="vertical" > - - <ImageView - android:id="@+id/group_divider" - android:layout_width="match_parent" - android:layout_height="1dip" - android:layout_marginTop="4dip" - android:layout_marginBottom="4dip" - android:background="@drawable/list_divider_material" /> - - <LinearLayout - android:id="@+id/content" - android:layout_width="match_parent" - android:layout_height="?attr/dropdownListPreferredItemHeight" - android:paddingEnd="16dip" - android:duplicateParentState="true" > - - <!-- Icon will be inserted here. --> - - <!-- The title and summary have some gap between them, - and this 'group' should be centered vertically. --> - <RelativeLayout - android:layout_width="0dip" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:duplicateParentState="true"> - - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_alignParentStart="true" - android:textAppearance="?attr/textAppearanceLargePopupMenu" - android:singleLine="true" - android:duplicateParentState="true" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:textAlignment="viewStart" /> - - <TextView - android:id="@+id/shortcut" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/title" - android:layout_alignParentStart="true" - android:textAppearance="?attr/textAppearanceSmallPopupMenu" - android:singleLine="true" - android:duplicateParentState="true" - android:textAlignment="viewStart" /> - - </RelativeLayout> - - <ImageView - android:id="@+id/submenuarrow" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="8dp" - android:scaleType="center" - android:visibility="gone" /> - - <!-- Checkbox, and/or radio button will be inserted here. --> - - </LinearLayout> - -</com.android.internal.view.menu.ListMenuItemView> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index b262ebd3feea..0df49dc6e0e8 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2300,7 +2300,7 @@ ensure that the status bar has enough contrast with the contents of this app, and set an appropriate effective bar background accordingly. See: {@link android.R.attr#enforceStatusBarContrast} - <p>If the app targets + <p>If the window belongs to an app targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above, this attribute is ignored. @deprecated Draw proper background behind @@ -2320,7 +2320,7 @@ ensure that the navigation bar has enough contrast with the contents of this app, and set an appropriate effective bar background accordingly. See: {@link android.R.attr#enforceNavigationBarContrast} - <p>If the app targets + <p>If the window belongs to an app targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above, this attribute is ignored. @deprecated Draw proper background behind @@ -2335,7 +2335,7 @@ have been requested to be translucent with {@link android.R.attr#windowTranslucentNavigation}. Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}. - <p>If the app targets + <p>If the window belongs to an app targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above, this attribute is ignored. @deprecated Draw proper background behind @@ -2431,7 +2431,9 @@ <!-- Controls how the window is laid out if there is a {@code DisplayCutout}. <p> - Defaults to {@code default}. + Defaults to {@code default}. But if the window fills the screen, and it belongs to an app + targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or + above, the behavior will be the same as specifying {@code always} regardless. <p> See also {@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode @@ -2528,8 +2530,8 @@ <!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement. - <p>If this is false, the edge-to-edge enforcement will be applied to the window if its - app targets + <p>If this is false, the edge-to-edge enforcement will be applied to the window if it + belongs to an app targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above. The affected behaviors are: <ul> @@ -2537,9 +2539,8 @@ through the {@link android.view.WindowInsets} to the content view, as if calling {@link android.view.Window#setDecorFitsSystemWindows(boolean)} with false. <li>{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode} of - the non-floating windows will be set to {@link + the fill-screen windows will behave as specifying {@link android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS}. - Changing it to other values will cause {@link lang.IllegalArgumentException}. <li>The framework will set {@link android.R.attr#statusBarColor}, {@link android.R.attr#navigationBarColor}, and {@link android.R.attr#navigationBarDividerColor} to transparent. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a622d36edc6a..1a618700cd17 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3009,6 +3009,10 @@ <!-- Maximum number of users we allow to be running at a time --> <integer name="config_multiuserMaxRunningUsers">3</integer> + <!-- Number of seconds of uptime after a full user enters the background before we attempt to + stop it due to inactivity. Set to -1 to disable scheduling stopping background users. --> + <integer name="config_backgroundUserScheduledStopTimeSecs">1800</integer> <!-- 30 minutes --> + <!-- Whether to delay user data locking for background user. If false, user switched-out from user switching will still be in running state until config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c4033f2d680a..ead582721826 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -488,6 +488,7 @@ <java-symbol type="integer" name="config_lockSoundVolumeDb" /> <java-symbol type="integer" name="config_multiuserMaximumUsers" /> <java-symbol type="integer" name="config_multiuserMaxRunningUsers" /> + <java-symbol type="integer" name="config_backgroundUserScheduledStopTimeSecs" /> <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" /> <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" /> <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" /> @@ -1527,7 +1528,6 @@ <java-symbol type="layout" name="number_picker" /> <java-symbol type="layout" name="permissions_package_list_item" /> <java-symbol type="layout" name="popup_menu_item_layout" /> - <java-symbol type="layout" name="popup_menu_item_layout_material" /> <java-symbol type="layout" name="popup_menu_header_item_layout" /> <java-symbol type="layout" name="remote_views_adapter_default_loading_view" /> <java-symbol type="layout" name="search_bar" /> diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 8506905e6ca0..f8c2d6aee84a 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -23,15 +23,19 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.Activity; +import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; @@ -45,6 +49,7 @@ import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.DisplayInfo; import android.window.ActivityWindowInfo; +import android.window.WindowTokenClient; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -55,6 +60,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -180,4 +186,36 @@ public class ClientTransactionListenerControllerTest { verify(mActivityWindowInfoListener, never()).accept(any(), any()); } + + @Test + public void testWindowTokenClient_onConfigurationChanged() { + doNothing().when(mController).onContextConfigurationPreChanged(any()); + doNothing().when(mController).onContextConfigurationPostChanged(any()); + + final WindowTokenClient windowTokenClient = spy(new WindowTokenClient()); + final Context context = mock(Context.class); + windowTokenClient.attachContext(context); + + doReturn(mController).when(windowTokenClient).getClientTransactionListenerController(); + doNothing().when(windowTokenClient).onConfigurationChangedInner(any(), any(), anyInt(), + anyBoolean()); + + // Not trigger when shouldReportConfigChange is false. + windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */, + false /* shouldReportConfigChange*/); + + verify(mController, never()).onContextConfigurationPreChanged(any()); + verify(mController, never()).onContextConfigurationPostChanged(any()); + + // Trigger in order when shouldReportConfigChange is true. + clearInvocations(windowTokenClient); + final InOrder inOrder = inOrder(mController, windowTokenClient); + windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */, + true /* shouldReportConfigChange*/); + + inOrder.verify(mController).onContextConfigurationPreChanged(context); + inOrder.verify(windowTokenClient).onConfigurationChangedInner(context, mConfiguration, + 123 /* newDisplayId */, true /* shouldReportConfigChange*/); + inOrder.verify(mController).onContextConfigurationPostChanged(context); + } } diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index 3a872b50af75..5bf88da1b3bb 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -28,7 +28,9 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -97,7 +99,7 @@ public class FaceManagerTest { mLooper = new TestLooper(); mHandler = new Handler(mLooper.getLooper()); - when(mContext.getMainLooper()).thenReturn(mLooper.getLooper()); + when(mContext.getMainThreadHandler()).thenReturn(mHandler); when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME); when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG); when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); @@ -210,6 +212,39 @@ public class FaceManagerTest { verify(mFaceDetectionCallback).onDetectionError(anyInt()); } + @Test + public void authenticate_onErrorCanceled() throws RemoteException { + final FaceManager.AuthenticationCallback authenticationCallback1 = mock( + FaceManager.AuthenticationCallback.class); + final FaceManager.AuthenticationCallback authenticationCallback2 = mock( + FaceManager.AuthenticationCallback.class); + + final ArgumentCaptor<IFaceServiceReceiver> faceServiceReceiverArgumentCaptor = + ArgumentCaptor.forClass(IFaceServiceReceiver.class); + + mFaceManager.authenticate(null, new CancellationSignal(), + authenticationCallback1, mHandler, + new FaceAuthenticateOptions.Builder().build()); + mFaceManager.authenticate(null, new CancellationSignal(), + authenticationCallback2, mHandler, + new FaceAuthenticateOptions.Builder().build()); + + verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L), + faceServiceReceiverArgumentCaptor.capture(), any()); + + final List<IFaceServiceReceiver> faceServiceReceivers = + faceServiceReceiverArgumentCaptor.getAllValues(); + faceServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + + verify(authenticationCallback1).onAuthenticationError(eq(5), anyString()); + verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString()); + + faceServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + verify(authenticationCallback2).onAuthenticationError(eq(5), anyString()); + } + private void initializeProperties() throws RemoteException { verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture()); diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java index ce7d6a95c2f4..c3ea7d38e2f1 100644 --- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java +++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java @@ -26,7 +26,9 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -93,6 +95,7 @@ public class FingerprintManagerTest { mHandler = new Handler(mLooper.getLooper()); when(mContext.getMainLooper()).thenReturn(mLooper.getLooper()); + when(mContext.getMainThreadHandler()).thenReturn(mHandler); when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME); when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG); when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); @@ -187,4 +190,38 @@ public class FingerprintManagerTest { verify(mFingerprintDetectionCallback).onDetectionError(anyInt()); } + + @Test + public void authenticate_onErrorCanceled() throws RemoteException { + final FingerprintManager.AuthenticationCallback authenticationCallback1 = mock( + FingerprintManager.AuthenticationCallback.class); + final FingerprintManager.AuthenticationCallback authenticationCallback2 = mock( + FingerprintManager.AuthenticationCallback.class); + + final ArgumentCaptor<IFingerprintServiceReceiver> fingerprintServiceReceiverArgumentCaptor = + ArgumentCaptor.forClass(IFingerprintServiceReceiver.class); + + mFingerprintManager.authenticate(null, new CancellationSignal(), + authenticationCallback1, mHandler, + new FingerprintAuthenticateOptions.Builder().build()); + mFingerprintManager.authenticate(null, new CancellationSignal(), + authenticationCallback2, mHandler, + new FingerprintAuthenticateOptions.Builder().build()); + + verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L), + fingerprintServiceReceiverArgumentCaptor.capture(), any()); + + final List<IFingerprintServiceReceiver> fingerprintServiceReceivers = + fingerprintServiceReceiverArgumentCaptor.getAllValues(); + fingerprintServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + + verify(authenticationCallback1).onAuthenticationError(eq(5), anyString()); + verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString()); + + fingerprintServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + + verify(authenticationCallback2).onAuthenticationError(eq(5), anyString()); + } } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 80fef6ca801e..ccebd0336ea6 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1258,18 +1258,6 @@ public class ViewRootImplTest { sInstrumentation.waitForIdleSync(); mViewRootImpl = mView.getViewRootImpl(); - waitForFrameRateCategoryToSettle(mView); - - sInstrumentation.runOnMainSync(() -> { - assertEquals(FRAME_RATE_CATEGORY_DEFAULT, - mViewRootImpl.getPreferredFrameRateCategory()); - mView.invalidate(); - int expected = toolkitFrameRateDefaultNormalReadOnly() - ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH; - runAfterDraw(() -> assertEquals(expected, - mViewRootImpl.getLastPreferredFrameRateCategory())); - }); - waitForAfterDraw(); waitForFrameRateCategoryToSettle(mView); diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java index 82251b841c6d..4921e4a4a061 100644 --- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java +++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java @@ -20,7 +20,6 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; @@ -82,14 +81,8 @@ public final class PhoneWindowTest { createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset); installDecor(); - if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0 - && !mPhoneWindow.isFloating()) { - assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode, - is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)); - } else { - assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode, - is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT)); - } + assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode, + is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT)); } @Test diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index eebd13370321..77b8663861ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -16,6 +16,9 @@ package com.android.wm.shell.recents; +import android.annotation.Nullable; +import android.graphics.Color; + import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -40,4 +43,12 @@ public interface RecentTasks { */ default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) { } + + /** + * Sets a background color on the transition root layered behind the outgoing task. {@code null} + * may be used to clear any previously set colors to avoid showing a background at all. The + * color is always shown at full opacity. + */ + default void setTransitionBackgroundColor(@Nullable Color color) { + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 0c99aed6852e..e7d9812e5393 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -30,6 +30,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; @@ -476,6 +477,16 @@ public class RecentTasksController implements TaskStackListenerCallback, }); }); } + + @Override + public void setTransitionBackgroundColor(@Nullable Color color) { + mMainExecutor.execute(() -> { + if (mTransitionHandler == null) { + return; + } + mTransitionHandler.setTransitionBackgroundColor(color); + }); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 24cf3706e25a..c625b69deac0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -36,6 +36,7 @@ import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.PendingIntent; import android.content.Intent; +import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -56,6 +57,8 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.protolog.common.ProtoLog; @@ -92,6 +95,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>(); private final HomeTransitionObserver mHomeTransitionObserver; + private @Nullable Color mBackgroundColor; public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, @Nullable RecentTasksController recentTasksController, @@ -123,6 +127,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mStateListeners.add(listener); } + /** + * Sets a background color on the transition root layered behind the outgoing task. {@code null} + * may be used to clear any previously set colors to avoid showing a background at all. The + * color is always shown at full opacity. + */ + public void setTransitionBackgroundColor(@Nullable Color color) { + mBackgroundColor = color; + } + @VisibleForTesting public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener) { @@ -469,6 +482,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final int belowLayers = info.getChanges().size(); final int middleLayers = info.getChanges().size() * 2; final int aboveLayers = info.getChanges().size() * 3; + + // Add a background color to each transition root in this transition. + if (mBackgroundColor != null) { + info.getChanges().stream() + .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info)) + .distinct() + .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash()) + .forEach((root) -> createBackgroundSurface(t, root, middleLayers)); + } + for (int i = 0; i < info.getChanges().size(); ++i) { final TransitionInfo.Change change = info.getChanges().get(i); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -1107,6 +1130,29 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return true; } + private void createBackgroundSurface(SurfaceControl.Transaction transaction, + SurfaceControl parent, int layer) { + if (mBackgroundColor == null) { + return; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " adding background color to layer=%d", layer); + final SurfaceControl background = new SurfaceControl.Builder() + .setName("recents_background") + .setColorLayer() + .setOpaque(true) + .setParent(parent) + .build(); + transaction.setColor(background, colorToFloatArray(mBackgroundColor)); + transaction.setLayer(background, layer); + transaction.setAlpha(background, 1F); + transaction.show(background); + } + + private static float[] colorToFloatArray(@NonNull Color color) { + return new float[]{color.red(), color.green(), color.blue()}; + } + private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { if (!sendUserLeaveHint && task.isLeaf()) { diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 5331046dd0e3..6593533a843a 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -205,6 +205,10 @@ public final class MediaDrm implements AutoCloseable { * media container format specified by mimeType at the requested * security level. * + * Calling this method while the application is running on the physical Android device or a + * {@link android.companion.virtual.VirtualDevice} may lead to different results, based on + * the different DRM capabilities of the devices. + * * @param uuid The UUID of the crypto scheme. * @param mimeType The MIME type of the media container, e.g. "video/mp4" * or "video/webm" @@ -1400,6 +1404,10 @@ public final class MediaDrm implements AutoCloseable { * Open a new session with the MediaDrm object. A session ID is returned. * By default, sessions are opened at the native security level of the device. * + * If the application is currently running on a {@link android.companion.virtual.VirtualDevice} + * the security level will be adjusted accordingly to the maximum supported level for the + * display. + * * @throws NotProvisionedException if provisioning is needed * @throws ResourceBusyException if required resources are in use */ @@ -1422,6 +1430,10 @@ public final class MediaDrm implements AutoCloseable { * can be queried using {@link #getSecurityLevel}. A session * ID is returned. * + * If the application is currently running on a {@link android.companion.virtual.VirtualDevice} + * the security level will be adjusted accordingly to the maximum supported level for the + * display. + * * @param level the new security level * @throws NotProvisionedException if provisioning is needed * @throws ResourceBusyException if required resources are in use @@ -2180,6 +2192,11 @@ public final class MediaDrm implements AutoCloseable { * Returns a value that may be passed as a parameter to {@link #openSession(int)} * requesting that the session be opened at the maximum security level of * the device. + * + * This security level is only valid for the application running on the physical Android + * device (e.g. {@link android.content.Context#DEVICE_ID_DEFAULT}). While running on a + * {@link android.companion.virtual.VirtualDevice} the maximum supported security level + * might be different. */ public static final int getMaxSecurityLevel() { return SECURITY_LEVEL_MAX; diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 94fce797f5d6..8609c4df1298 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -82,6 +82,7 @@ cc_library_shared { "libhidlbase", "libsonivox", "server_configurable_flags", + "android.companion.virtual.virtualdevice_aidl-cpp", "android.hardware.cas@1.0", "android.hardware.cas.native@1.0", "android.hardware.drm@1.3", @@ -100,6 +101,7 @@ cc_library_shared { static_libs: [ "libgrallocusage", "libmedia_midiiowrapper", + "android.companion.virtualdevice.flags-aconfig-cc", "android.media.playback.flags-aconfig-cc", ], diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 1c25080939da..48cd53dc44d7 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -27,6 +27,8 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <android_companion_virtualdevice_flags.h> +#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h> #include <android/hardware/drm/1.3/IDrmFactory.h> #include <binder/Parcel.h> #include <binder/PersistableBundle.h> @@ -41,8 +43,10 @@ #include <map> #include <string> +using ::android::companion::virtualnative::IVirtualDeviceManagerNative; using ::android::os::PersistableBundle; namespace drm = ::android::hardware::drm; +namespace virtualdevice_flags = android::companion::virtualdevice::flags; namespace android { @@ -1045,6 +1049,26 @@ DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) { return level; } +std::vector<int> getVirtualDeviceIds() { + if (!virtualdevice_flags::device_aware_drm()) { + ALOGW("Device-aware DRM flag disabled."); + return std::vector<int>(); + } + + sp<IBinder> binder = + defaultServiceManager()->checkService(String16("virtualdevice_native")); + if (binder != nullptr) { + auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder); + std::vector<int> deviceIds; + const uid_t uid = IPCThreadState::self()->getCallingUid(); + vdm->getDeviceIdsForUid(uid, &deviceIds); + return deviceIds; + } else { + ALOGW("Cannot get virtualdevice_native service"); + return std::vector<int>(); + } +} + static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) { sp<IDrm> drm = android::DrmUtils::MakeDrm(); if (drm == NULL) return env->NewByteArray(0); @@ -1081,6 +1105,15 @@ static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( } DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel); + if (getVirtualDeviceIds().size() > 0) { + // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at + // higher security levels decode output cannot be captured and + // streamed to virtual devices rendered on virtual displays. + if (securityLevel > DrmPlugin::kSecurityLevelSwSecureCrypto) { + return false; + } + } + bool isSupported; status_t err = JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType, securityLevel, &isSupported); @@ -1106,6 +1139,16 @@ static jbyteArray android_media_MediaDrm_openSession( return NULL; } + if (getVirtualDeviceIds().size() > 0) { + // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at + // higher security levels decode output cannot be captured and + // streamed to virtual devices rendered on virtual displays. + if (level == DrmPlugin::kSecurityLevelMax || + level > DrmPlugin::kSecurityLevelSwSecureCrypto) { + level = DrmPlugin::kSecurityLevelSwSecureCrypto; + } + } + DrmStatus err = drm->openSession(level, sessionId); if (throwExceptionAsNecessary(env, drm, err, "Failed to open session")) { diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h index a64e3f2a7cf0..36cba2dc3c7e 100644 --- a/media/jni/android_media_MediaDrm.h +++ b/media/jni/android_media_MediaDrm.h @@ -19,6 +19,8 @@ #include "jni.h" +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> #include <media/stagefright/foundation/ABase.h> #include <mediadrm/IDrm.h> #include <mediadrm/IDrmClient.h> diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index b57d54821b75..7cd7e7ab49a9 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -35,6 +35,7 @@ import android.nfc.INfcDta; import android.nfc.INfcWlcStateListener; import android.nfc.NfcAntennaInfo; import android.nfc.WlcListenerDeviceInfo; +import android.nfc.cardemulation.PollingFrame; import android.os.Bundle; /** @@ -101,7 +102,7 @@ interface INfcAdapter void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags); - void notifyPollingLoop(in Bundle frame); + void notifyPollingLoop(in PollingFrame frame); void notifyHceDeactivated(); int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload); void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index b44a71bf9be0..29867d924c78 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -2803,12 +2803,11 @@ public final class NfcAdapter { @TestApi @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) { - Bundle frame = pollingFrame.toBundle(); try { if (sService == null) { attemptDeadServiceRecovery(null); } - sService.notifyPollingLoop(frame); + sService.notifyPollingLoop(pollingFrame); } catch (RemoteException e) { attemptDeadServiceRecovery(e); // Try one more time @@ -2817,7 +2816,7 @@ public final class NfcAdapter { return; } try { - sService.notifyPollingLoop(frame); + sService.notifyPollingLoop(pollingFrame); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover NFC Service."); } diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java index 61037a2e7e9e..f674b06ad33d 100644 --- a/nfc/java/android/nfc/cardemulation/HostApduService.java +++ b/nfc/java/android/nfc/cardemulation/HostApduService.java @@ -325,15 +325,12 @@ public abstract class HostApduService extends Service { } break; case MSG_POLLING_LOOP: - ArrayList<Bundle> frames = - msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE, - Bundle.class); - ArrayList<PollingFrame> pollingFrames = - new ArrayList<PollingFrame>(frames.size()); - for (Bundle frame : frames) { - pollingFrames.add(new PollingFrame(frame)); + if (android.nfc.Flags.nfcReadPollingLoop()) { + ArrayList<PollingFrame> pollingFrames = + msg.getData().getParcelableArrayList( + KEY_POLLING_LOOP_FRAMES_BUNDLE, PollingFrame.class); + processPollingFrames(pollingFrames); } - processPollingFrames(pollingFrames); break; default: super.handleMessage(msg); diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.aidl b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl new file mode 100644 index 000000000000..8e09f8baaff2 --- /dev/null +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 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 android.nfc.cardemulation; + +parcelable PollingFrame;
\ No newline at end of file diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java index c6861bf97443..b52faba79ed7 100644 --- a/nfc/java/android/nfc/cardemulation/PollingFrame.java +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java @@ -101,47 +101,37 @@ public final class PollingFrame implements Parcelable{ /** * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of * polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE"; + private static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE"; /** * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from * the polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA"; + private static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA"; /** * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of * the polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide - */ + */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN"; + private static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN"; /** * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of * the polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide - */ + */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP"; + private static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP"; /** * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered * autoTransact in the Bundle included in MSG_POLLING_LOOP. - * - * @hide - */ + */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT = + private static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT = "android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT"; @@ -151,7 +141,7 @@ public final class PollingFrame implements Parcelable{ private final int mGain; @DurationMillisLong private final long mTimestamp; - private final boolean mTriggeredAutoTransact; + private boolean mTriggeredAutoTransact; public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR = new Parcelable.Creator<>() { @@ -166,7 +156,7 @@ public final class PollingFrame implements Parcelable{ } }; - PollingFrame(Bundle frame) { + private PollingFrame(Bundle frame) { mType = frame.getInt(KEY_POLLING_LOOP_TYPE); byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA); mData = (data == null) ? new byte[0] : data; @@ -239,6 +229,13 @@ public final class PollingFrame implements Parcelable{ } /** + * @hide + */ + public void setTriggeredAutoTransact(boolean triggeredAutoTransact) { + mTriggeredAutoTransact = triggeredAutoTransact; + } + + /** * Returns whether this frame triggered the device to automatically disable observe mode and * allow one transaction. */ @@ -257,11 +254,9 @@ public final class PollingFrame implements Parcelable{ } /** - * - * @hide * @return a Bundle representing this frame */ - public Bundle toBundle() { + private Bundle toBundle() { Bundle frame = new Bundle(); frame.putInt(KEY_POLLING_LOOP_TYPE, getType()); if (getVendorSpecificGain() != -1) { diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java deleted file mode 100644 index fa4d6afc03d3..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2024 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 android.utils; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -import java.io.File; -import java.util.List; -import java.util.Objects; - -/** - * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java - * - * @hide - */ -public class ArrayUtils { - private ArrayUtils() { /* cannot be instantiated */ } - public static final File[] EMPTY_FILE = new File[0]; - - - /** - * Return first index of {@code value} in {@code array}, or {@code -1} if - * not found. - */ - public static <T> int indexOf(@Nullable T[] array, T value) { - if (array == null) return -1; - for (int i = 0; i < array.length; i++) { - if (Objects.equals(array[i], value)) return i; - } - return -1; - } - - /** @hide */ - public static @NonNull File[] defeatNullable(@Nullable File[] val) { - return (val != null) ? val : EMPTY_FILE; - } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable int[] array) { - return array == null || array.length == 0; - } - - /** - * True if the byte array is null or has length 0. - */ - public static boolean isEmpty(@Nullable byte[] array) { - return array == null || array.length == 0; - } - - /** - * Converts from List of bytes to byte array - * @param list - * @return byte[] - */ - public static byte[] toPrimitive(List<byte[]> list) { - if (list.size() == 0) { - return new byte[0]; - } - int byteLen = list.get(0).length; - byte[] array = new byte[list.size() * byteLen]; - for (int i = 0; i < list.size(); i++) { - for (int j = 0; j < list.get(i).length; j++) { - array[i * byteLen + j] = list.get(i)[j]; - } - } - return array; - } - - /** - * Adds value to given array if not already present, providing set-like - * behavior. - */ - public static @NonNull int[] appendInt(@Nullable int[] cur, int val) { - return appendInt(cur, val, false); - } - - /** - * Adds value to given array. - */ - public static @NonNull int[] appendInt(@Nullable int[] cur, int val, - boolean allowDuplicates) { - if (cur == null) { - return new int[] { val }; - } - final int n = cur.length; - if (!allowDuplicates) { - for (int i = 0; i < n; i++) { - if (cur[i] == val) { - return cur; - } - } - } - int[] ret = new int[n + 1]; - System.arraycopy(cur, 0, ret, 0, n); - ret[n] = val; - return ret; - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java deleted file mode 100644 index afcf6895fd0d..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * * Copyright (C) 2024 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 android.utils; - -import android.annotation.NonNull; -import android.os.Handler; -import android.os.HandlerThread; - -import com.android.internal.annotations.GuardedBy; - -import java.util.concurrent.Executor; - -/** - * Thread for asynchronous event processing. This thread is configured as - * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU - * resources will be dedicated to it, and it will "have less chance of impacting - * the responsiveness of the user interface." - * <p> - * This thread is best suited for tasks that the user is not actively waiting - * for, or for tasks that the user expects to be executed eventually. - * - * @see com.android.internal.os.BackgroundThread - * - * TODO: b/326916057 depend on modules-utils-backgroundthread instead - * @hide - */ -public final class BackgroundThread extends HandlerThread { - private static final Object sLock = new Object(); - - @GuardedBy("sLock") - private static BackgroundThread sInstance; - @GuardedBy("sLock") - private static Handler sHandler; - @GuardedBy("sLock") - private static HandlerExecutor sHandlerExecutor; - - private BackgroundThread() { - super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND); - } - - @GuardedBy("sLock") - private static void ensureThreadLocked() { - if (sInstance == null) { - sInstance = new BackgroundThread(); - sInstance.start(); - sHandler = new Handler(sInstance.getLooper()); - sHandlerExecutor = new HandlerExecutor(sHandler); - } - } - - /** - * Get the singleton instance of this class. - * - * @return the singleton instance of this class - */ - @NonNull - public static BackgroundThread get() { - synchronized (sLock) { - ensureThreadLocked(); - return sInstance; - } - } - - /** - * Get the singleton {@link Handler} for this class. - * - * @return the singleton {@link Handler} for this class. - */ - @NonNull - public static Handler getHandler() { - synchronized (sLock) { - ensureThreadLocked(); - return sHandler; - } - } - - /** - * Get the singleton {@link Executor} for this class. - * - * @return the singleton {@link Executor} for this class. - */ - @NonNull - public static Executor getExecutor() { - synchronized (sLock) { - ensureThreadLocked(); - return sHandlerExecutor; - } - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java deleted file mode 100644 index e4923bfc4ecb..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2024 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 android.utils; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Bits and pieces copied from hidden API of android.os.FileUtils. - * - * @hide - */ -public class FileUtils { - /** - * Read a text file into a String, optionally limiting the length. - * - * @param file to read (will not seek, so things like /proc files are OK) - * @param max length (positive for head, negative of tail, 0 for no limit) - * @param ellipsis to add of the file was truncated (can be null) - * @return the contents of the file, possibly truncated - * @throws IOException if something goes wrong reading the file - * @hide - */ - public static @Nullable String readTextFile(@Nullable File file, @Nullable int max, - @Nullable String ellipsis) throws IOException { - InputStream input = new FileInputStream(file); - // wrapping a BufferedInputStream around it because when reading /proc with unbuffered - // input stream, bytes read not equal to buffer size is not necessarily the correct - // indication for EOF; but it is true for BufferedInputStream due to its implementation. - BufferedInputStream bis = new BufferedInputStream(input); - try { - long size = file.length(); - if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes - if (size > 0 && (max == 0 || size < max)) max = (int) size; - byte[] data = new byte[max + 1]; - int length = bis.read(data); - if (length <= 0) return ""; - if (length <= max) return new String(data, 0, length); - if (ellipsis == null) return new String(data, 0, max); - return new String(data, 0, max) + ellipsis; - } else if (max < 0) { // "tail" mode: keep the last N - int len; - boolean rolled = false; - byte[] last = null; - byte[] data = null; - do { - if (last != null) rolled = true; - byte[] tmp = last; - last = data; - data = tmp; - if (data == null) data = new byte[-max]; - len = bis.read(data); - } while (len == data.length); - - if (last == null && len <= 0) return ""; - if (last == null) return new String(data, 0, len); - if (len > 0) { - rolled = true; - System.arraycopy(last, len, last, 0, last.length - len); - System.arraycopy(data, 0, last, last.length - len, len); - } - if (ellipsis == null || !rolled) return new String(last); - return ellipsis + new String(last); - } else { // "cat" mode: size unknown, read it all in streaming fashion - ByteArrayOutputStream contents = new ByteArrayOutputStream(); - int len; - byte[] data = new byte[1024]; - do { - len = bis.read(data); - if (len > 0) contents.write(data, 0, len); - } while (len == data.length); - return contents.toString(); - } - } finally { - bis.close(); - input.close(); - } - } - - /** - * Perform an fsync on the given FileOutputStream. The stream at this - * point must be flushed but not yet closed. - * - * @hide - */ - public static boolean sync(FileOutputStream stream) { - try { - if (stream != null) { - stream.getFD().sync(); - } - return true; - } catch (IOException e) { - } - return false; - } - - /** - * List the files in the directory or return empty file. - * - * @hide - */ - public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) { - return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles()) - : ArrayUtils.EMPTY_FILE; - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java deleted file mode 100644 index fdb15e2333d5..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2024 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 android.utils; - -import android.annotation.NonNull; -import android.os.Handler; - -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; - -/** - * An adapter {@link Executor} that posts all executed tasks onto the given - * {@link Handler}. - * - * TODO: b/326916057 depend on modules-utils-backgroundthread instead - * @hide - */ -public class HandlerExecutor implements Executor { - private final Handler mHandler; - - public HandlerExecutor(@NonNull Handler handler) { - mHandler = Objects.requireNonNull(handler); - } - - @Override - public void execute(Runnable command) { - if (!mHandler.post(command)) { - throw new RejectedExecutionException(mHandler + " is shutting down"); - } - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java deleted file mode 100644 index 5cdc2536129a..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2024 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 android.utils; - -import libcore.util.EmptyArray; - -import java.util.NoSuchElementException; - -/** - * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java - * - * @hide - */ -public class LongArrayQueue { - - private long[] mValues; - private int mSize; - private int mHead; - private int mTail; - - private long[] newUnpaddedLongArray(int num) { - return new long[num]; - } - /** - * Initializes a queue with the given starting capacity. - * - * @param initialCapacity the capacity. - */ - public LongArrayQueue(int initialCapacity) { - if (initialCapacity == 0) { - mValues = EmptyArray.LONG; - } else { - mValues = newUnpaddedLongArray(initialCapacity); - } - mSize = 0; - mHead = mTail = 0; - } - - /** - * Initializes a queue with default starting capacity. - */ - public LongArrayQueue() { - this(16); - } - - /** @hide */ - public static int growSize(int currentSize) { - return currentSize <= 4 ? 8 : currentSize * 2; - } - - private void grow() { - if (mSize < mValues.length) { - throw new IllegalStateException("Queue not full yet!"); - } - final int newSize = growSize(mSize); - final long[] newArray = newUnpaddedLongArray(newSize); - final int r = mValues.length - mHead; // Number of elements on and to the right of head. - System.arraycopy(mValues, mHead, newArray, 0, r); - System.arraycopy(mValues, 0, newArray, r, mHead); - mValues = newArray; - mHead = 0; - mTail = mSize; - } - - /** - * Returns the number of elements in the queue. - */ - public int size() { - return mSize; - } - - /** - * Removes all elements from this queue. - */ - public void clear() { - mSize = 0; - mHead = mTail = 0; - } - - /** - * Adds a value to the tail of the queue. - * - * @param value the value to be added. - */ - public void addLast(long value) { - if (mSize == mValues.length) { - grow(); - } - mValues[mTail] = value; - mTail = (mTail + 1) % mValues.length; - mSize++; - } - - /** - * Removes an element from the head of the queue. - * - * @return the element at the head of the queue. - * @throws NoSuchElementException if the queue is empty. - */ - public long removeFirst() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - final long ret = mValues[mHead]; - mHead = (mHead + 1) % mValues.length; - mSize--; - return ret; - } - - /** - * Returns the element at the given position from the head of the queue, where 0 represents the - * head of the queue. - * - * @param position the position from the head of the queue. - * @return the element found at the given position. - * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or - * {@code position} >= {@link #size()} - */ - public long get(int position) { - if (position < 0 || position >= mSize) { - throw new IndexOutOfBoundsException("Index " + position - + " not valid for a queue of size " + mSize); - } - final int index = (mHead + position) % mValues.length; - return mValues[index]; - } - - /** - * Returns the element at the head of the queue, without removing it. - * - * @return the element at the head of the queue. - * @throws NoSuchElementException if the queue is empty - */ - public long peekFirst() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - return mValues[mHead]; - } - - /** - * Returns the element at the tail of the queue. - * - * @return the element at the tail of the queue. - * @throws NoSuchElementException if the queue is empty. - */ - public long peekLast() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1; - return mValues[index]; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - if (mSize <= 0) { - return "{}"; - } - - final StringBuilder buffer = new StringBuilder(mSize * 64); - buffer.append('{'); - buffer.append(get(0)); - for (int i = 1; i < mSize; i++) { - buffer.append(", "); - buffer.append(get(i)); - } - buffer.append('}'); - return buffer.toString(); - } -} diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java deleted file mode 100644 index dbbef61f6777..000000000000 --- a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2024 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 android.utils; - -import android.annotation.NonNull; -import android.system.ErrnoException; -import android.system.Os; - -import com.android.modules.utils.TypedXmlPullParser; - -import libcore.util.XmlObjectFactory; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java - * - * @hide - */ -public class XmlUtils { - - private static final String STRING_ARRAY_SEPARATOR = ":"; - - /** @hide */ - public static final void beginDocument(XmlPullParser parser, String firstElementName) - throws XmlPullParserException, IOException { - int type; - while ((type = parser.next()) != parser.START_TAG - && type != parser.END_DOCUMENT) { - // Do nothing - } - - if (type != parser.START_TAG) { - throw new XmlPullParserException("No start tag found"); - } - - if (!parser.getName().equals(firstElementName)) { - throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() - + ", expected " + firstElementName); - } - } - - /** @hide */ - public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) - throws IOException, XmlPullParserException { - for (;;) { - int type = parser.next(); - if (type == XmlPullParser.END_DOCUMENT - || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) { - return false; - } - if (type == XmlPullParser.START_TAG - && parser.getDepth() == outerDepth + 1) { - return true; - } - } - } - - private static XmlPullParser newPullParser() { - try { - XmlPullParser parser = XmlObjectFactory.newXmlPullParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - return parser; - } catch (XmlPullParserException e) { - throw new AssertionError(); - } - } - - /** @hide */ - public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in) - throws IOException { - final byte[] magic = new byte[4]; - if (in instanceof FileInputStream) { - try { - Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0); - } catch (ErrnoException e) { - throw e.rethrowAsIOException(); - } - } else { - if (!in.markSupported()) { - in = new BufferedInputStream(in); - } - in.mark(8); - in.read(magic); - in.reset(); - } - - final TypedXmlPullParser xml; - xml = (TypedXmlPullParser) newPullParser(); - try { - xml.setInput(in, "UTF_8"); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - return xml; - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 429bdbf5959b..7bc32419e070 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -317,6 +317,14 @@ class CredentialSelectorViewModel( ) } + fun createFlowOnMoreOptionsOnlySelectedOnCreationSelection() { + uiState = uiState.copy( + createCredentialUiState = uiState.createCredentialUiState?.copy( + currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION_ONLY, + ) + ) + } + fun createFlowOnBackCreationSelectionButtonSelected() { uiState = uiState.copy( createCredentialUiState = uiState.createCredentialUiState?.copy( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 4109079e20a5..50ebdd5e3ce7 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -47,9 +47,9 @@ import android.service.autofill.SaveRequest import android.service.credentials.CredentialProviderService import android.util.Log import android.content.Intent +import android.os.IBinder import android.view.autofill.AutofillId import android.view.autofill.AutofillManager -import android.view.autofill.IAutoFillManagerClient import android.widget.RemoteViews import android.widget.inline.InlinePresentationSpec import androidx.autofill.inline.v1.InlineSuggestionUi @@ -95,7 +95,7 @@ class CredentialAutofillService : AutofillService() { request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback, - autofillCallback: IAutoFillManagerClient + autofillCallback: IBinder ) { val context = request.fillContexts val structure = context[context.size - 1].structure @@ -160,7 +160,7 @@ class CredentialAutofillService : AutofillService() { CancellationSignal(), Executors.newSingleThreadExecutor(), outcome, - autofillCallback.asBinder() + autofillCallback ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index 9c74fd36c5c4..0d19a45ab82a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -209,7 +209,7 @@ private fun runBiometricFlow( onCancelFlowAndFinish: () -> Unit ) { try { - if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) { + if (!canCallBiometricPrompt(biometricDisplayInfo, context)) { onBiometricFailureFallback(biometricFlowType) return } @@ -249,40 +249,40 @@ private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? { * consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks * occur if *only* device credentials are available, to avoid going right into the PIN screen. * Note that if device credential is the only available modality but not requested, or if none - * of the requested modalities are available, we propagate the error to the provider instead of - * falling back and expect them to handle it as they would prior. - * // TODO(b/334197980) : Finalize error propagation/not propagation in real use cases + * of the requested modalities are available, we fallback to the normal flow to ensure a selector + * shows up. + * // TODO(b/334197980) : While we already fallback in cases the selector doesn't show, confirm + * // final plan. */ -private fun onlyUsingDeviceCredentials( +private fun canCallBiometricPrompt( biometricDisplayInfo: BiometricDisplayInfo, context: Context ): Boolean { val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { - return true + return false } - val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials( - allowedAuthenticators) + val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager - if (!allowedAuthContainsDeviceCredential) { - // At this point, allowed authenticators is requesting biometrics without device creds. - // Thus, a fallback mechanism will be displayed via our own negative button - "cancel". - // Beyond this point, fallbacks will occur if none of the stronger authenticators can - // be used. + if (biometricManager.canAuthenticate(allowedAuthenticators) != + BiometricManager.BIOMETRIC_SUCCESS) { return false } - val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager + if (ifOnlySupportsAtMostDeviceCredentials(biometricManager)) return false - if (allowedAuthContainsDeviceCredential && - biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) != + return true +} + +private fun ifOnlySupportsAtMostDeviceCredentials(biometricManager: BiometricManager): Boolean { + if (biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) != BiometricManager.BIOMETRIC_SUCCESS && biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) != - BiometricManager.BIOMETRIC_SUCCESS) { + BiometricManager.BIOMETRIC_SUCCESS + ) { return true } - return false } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index 149c14a24085..2c3c63bea95f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.outlined.Lock import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -47,7 +46,6 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.input.PasswordVisualTransformation @@ -55,7 +53,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.android.compose.theme.LocalAndroidColorScheme -import com.android.credentialmanager.R import com.android.credentialmanager.ui.theme.EntryShape import com.android.credentialmanager.ui.theme.Shapes @@ -321,6 +318,8 @@ fun CtaButtonRow( fun MoreOptionTopAppBar( text: String, onNavigationIconClicked: () -> Unit, + navigationIcon: ImageVector, + navigationIconContentDescription: String, bottomPadding: Dp, ) { Row( @@ -336,40 +335,6 @@ fun MoreOptionTopAppBar( contentAlignment = Alignment.Center, ) { Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = stringResource( - R.string.accessibility_back_arrow_button - ), - modifier = Modifier.size(24.dp).autoMirrored(), - tint = LocalAndroidColorScheme.current.onSurfaceVariant, - ) - } - } - LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp)) - } -} - -@Composable -fun MoreOptionTopAppBarWithCustomNavigation( - text: String, - onNavigationIconClicked: () -> Unit, - navigationIcon: ImageVector, - navigationIconContentDescription: String, - bottomPadding: Dp, -) { - Row( - modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding), - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton( - modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp), - onClick = onNavigationIconClicked - ) { - Box( - modifier = Modifier.size(48.dp), - contentAlignment = Alignment.Center, - ) { - Icon( imageVector = navigationIcon, contentDescription = navigationIconContentDescription, modifier = Modifier.size(24.dp).autoMirrored(), diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index a0915d22b613..282a1b5736ac 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -32,6 +32,8 @@ import androidx.compose.material3.Divider import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -107,7 +109,7 @@ fun CreateCredentialScreen( onCancelFlowAndFinish = viewModel::onUserCancel, onIllegalScreenStateAndFinish = viewModel::onIllegalUiState, onMoreOptionSelected = - viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection, + viewModel::createFlowOnMoreOptionsOnlySelectedOnCreationSelection, requestDisplayInfo = createCredentialUiState.requestDisplayInfo, enabledProviderInfo = createCredentialUiState .activeEntry?.activeProvider!!, @@ -120,6 +122,41 @@ fun CreateCredentialScreen( onBiometricPromptStateChange = viewModel::onBiometricPromptStateChange ) + CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard( + requestDisplayInfo = createCredentialUiState.requestDisplayInfo, + enabledProviderList = createCredentialUiState.enabledProviders, + disabledProviderList = createCredentialUiState.disabledProviders, + sortedCreateOptionsPairs = + createCredentialUiState.sortedCreateOptionsPairs, + onBackCreationSelectionButtonSelected = + viewModel::createFlowOnBackCreationSelectionButtonSelected, + onOptionSelected = + viewModel::createFlowOnEntrySelectedFromMoreOptionScreen, + onDisabledProvidersSelected = + viewModel::createFlowOnLaunchSettings, + onRemoteEntrySelected = viewModel::createFlowOnEntrySelected, + onLog = { viewModel.logUiEvent(it) }, + customTopAppBar = { MoreOptionTopAppBar( + text = stringResource( + R.string.save_credential_to_title, + when (createCredentialUiState.requestDisplayInfo + .type) { + CredentialType.PASSKEY -> + stringResource(R.string.passkey) + CredentialType.PASSWORD -> + stringResource(R.string.password) + CredentialType.UNKNOWN -> stringResource( + R.string.sign_in_info) + } + ), + onNavigationIconClicked = viewModel::onUserCancel, + bottomPadding = 16.dp, + navigationIcon = Icons.Filled.Close, + navigationIconContentDescription = stringResource( + R.string.accessibility_close_button + ) + )} + ) CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard( requestDisplayInfo = createCredentialUiState.requestDisplayInfo, enabledProviderList = createCredentialUiState.enabledProviders, @@ -207,22 +244,31 @@ fun MoreOptionsSelectionCard( onDisabledProvidersSelected: () -> Unit, onRemoteEntrySelected: (EntryInfo) -> Unit, onLog: @Composable (UiEventEnum) -> Unit, + customTopAppBar: (@Composable() () -> Unit)? = null ) { SheetContainerCard(topAppBar = { - MoreOptionTopAppBar( - text = stringResource( - R.string.save_credential_to_title, - when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> - stringResource(R.string.passkey) - CredentialType.PASSWORD -> - stringResource(R.string.password) - CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) - } - ), - onNavigationIconClicked = onBackCreationSelectionButtonSelected, - bottomPadding = 16.dp, - ) + if (customTopAppBar != null) { + customTopAppBar() + } else { + MoreOptionTopAppBar( + text = stringResource( + R.string.save_credential_to_title, + when (requestDisplayInfo.type) { + CredentialType.PASSKEY -> + stringResource(R.string.passkey) + CredentialType.PASSWORD -> + stringResource(R.string.password) + CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) + } + ), + onNavigationIconClicked = onBackCreationSelectionButtonSelected, + bottomPadding = 16.dp, + navigationIcon = Icons.Filled.ArrowBack, + navigationIconContentDescription = stringResource( + R.string.accessibility_back_arrow_button + ) + ) + } }) { // bottom padding already item { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index ddd4139b65b6..130937c70895 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -181,4 +181,5 @@ enum class CreateScreenState { MORE_OPTIONS_SELECTION, DEFAULT_PROVIDER_CONFIRMATION, EXTERNAL_ONLY_SELECTION, + MORE_OPTIONS_SELECTION_ONLY, } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index ce4f402700d3..c98bb5ed17c1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.material3.Divider @@ -71,7 +72,6 @@ import com.android.credentialmanager.common.ui.HeadlineText import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.ModalBottomSheet import com.android.credentialmanager.common.ui.MoreOptionTopAppBar -import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.common.ui.SnackbarActionText @@ -175,7 +175,7 @@ fun GetCredentialScreen( onBackButtonClicked = viewModel::onUserCancel, onCancel = viewModel::onUserCancel, onLog = { viewModel.logUiEvent(it) }, - customTopBar = { MoreOptionTopAppBarWithCustomNavigation( + customTopBar = { MoreOptionTopAppBar( text = stringResource( R.string.get_dialog_title_sign_in_options), onNavigationIconClicked = viewModel::onUserCancel, @@ -683,7 +683,10 @@ fun AllSignInOptionCard( text = stringResource(R.string.get_dialog_title_sign_in_options), onNavigationIconClicked = onBackButtonClicked, bottomPadding = 0.dp, - ) + navigationIcon = Icons.Filled.ArrowBack, + navigationIconContentDescription = stringResource( + R.string.accessibility_back_arrow_button + )) } }) { var isFirstSection = true diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml new file mode 100644 index 000000000000..fadcf7ba8699 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> +<resources> + <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight"> + <item name="elevationOverlayEnabled">true</item> + <item name="elevationOverlayColor">?attr/colorPrimary</item> + <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item> + <item name="colorAccent">@color/settingslib_materialColorPrimaryFixed</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml new file mode 100644 index 000000000000..0c2028744bf7 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> +<resources> + <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">20dp</item> + <item name="android:textColor">@color/settingslib_materialColorOnSurface</item> + </style> + + <style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed"> + <item name="android:textSize">36dp</item> + <item name="android:textColor">@color/settingslib_materialColorOnSurface</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml new file mode 100644 index 000000000000..7c9d1a47b7ef --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> +<resources> + <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight"> + <item name="elevationOverlayEnabled">true</item> + <item name="elevationOverlayColor">?attr/colorPrimary</item> + <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item> + <item name="colorAccent">@color/settingslib_materialColorPrimary</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml deleted file mode 100644 index a2b964882c7c..000000000000 --- a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml +++ /dev/null @@ -1,74 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2024 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. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" - android:clipToPadding="false"> - - <LinearLayout - android:id="@+id/icon_frame" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minWidth="56dp" - android:gravity="start|top" - android:orientation="horizontal" - android:paddingEnd="12dp" - android:paddingTop="16dp" - android:paddingBottom="4dp"> - <ImageView - android:id="@android:id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> - </LinearLayout> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - <TextView - android:id="@android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:textAlignment="viewStart" - android:paddingTop="16dp" - android:paddingBottom="8dp" - android:textColor="@color/settingslib_materialColorOnSurfaceVariant" - android:hyphenationFrequency="normalFast" - android:lineBreakWordStyle="phrase" - android:ellipsize="marquee" /> - - <com.android.settingslib.widget.LinkTextView - android:id="@+id/settingslib_learn_more" - android:text="@string/settingslib_learn_more_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:textAlignment="viewStart" - android:paddingBottom="8dp" - android:clickable="true" - android:visibility="gone" /> - </LinearLayout> - -</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml index c7fbb5f0374b..ea15a67e93cd 100644 --- a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml @@ -13,14 +13,11 @@ 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. - --> +--> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@color/settingslib_materialColorOnSurfaceVariant" - android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> -</vector> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:color="@color/settingslib_materialColorOnPrimaryContainer" + android:alpha="?android:attr/disabledAlpha" /> + <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml new file mode 100644 index 000000000000..ea15a67e93cd --- /dev/null +++ b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:color="@color/settingslib_materialColorOnPrimaryContainer" + android:alpha="?android:attr/disabledAlpha" /> + <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml new file mode 100644 index 000000000000..5192a9a53572 --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@color/settingslib_materialColorSecondaryFixed" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml new file mode 100644 index 000000000000..4b1683294a9a --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@color/settingslib_materialColorPrimaryFixed" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml index 81ddf29ac9df..1429e3bdc666 100644 --- a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml +++ b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - +<!--Deprecated. After sdk 35 don't use it.--> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@android:color/system_accent2_500" android:lStar="51" /> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml new file mode 100644 index 000000000000..eedc364ff54b --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Disabled status of thumb --> + <item android:state_enabled="false" + android:color="@color/settingslib_materialColorOutline" + android:alpha="?android:attr/disabledAlpha" /> + <!-- Toggle off status of thumb --> + <item android:state_checked="false" + android:color="@color/settingslib_materialColorOutline" /> + <!-- Enabled or toggle on status of thumb --> + <item android:color="@color/settingslib_track_on_color" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml index 037b80abc6f9..b46181e20eaa 100644 --- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> +<!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in light theme --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@android:color/system_neutral1_500" android:lStar="98" /> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml index 762bb31243a2..f0bcf0ab2abe 100644 --- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - +<!--Deprecated. After sdk 35 don't use it.--> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@android:color/system_neutral2_500" android:lStar="45" /> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml new file mode 100644 index 000000000000..eedc364ff54b --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Disabled status of thumb --> + <item android:state_enabled="false" + android:color="@color/settingslib_materialColorOutline" + android:alpha="?android:attr/disabledAlpha" /> + <!-- Toggle off status of thumb --> + <item android:state_checked="false" + android:color="@color/settingslib_materialColorOutline" /> + <!-- Enabled or toggle on status of thumb --> + <item android:color="@color/settingslib_track_on_color" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml new file mode 100644 index 000000000000..230eb7d30aea --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?android:attr/disabledAlpha" + android:color="@color/settingslib_materialColorOnSurface"/> + <item android:color="@color/settingslib_materialColorOnSurface"/> +</selector> diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml new file mode 100644 index 000000000000..5bd2a29ccf9a --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?android:attr/disabledAlpha" + android:color="@color/settingslib_materialColorOnSurfaceVariant"/> + <item android:color="@color/settingslib_materialColorOnSurfaceVariant"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml new file mode 100644 index 000000000000..3cb34354de17 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<layer-list + xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@android:id/background"> + <shape> + <corners android:radius="8dp" /> + <solid android:color="@color/settingslib_materialColorSurfaceVariant" /> + </shape> + </item> + + <item + android:id="@android:id/progress"> + <scale android:scaleWidth="100%" android:useIntrinsicSizeAsMinimum="true"> + <shape> + <corners android:radius="8dp" /> + <solid android:color="?android:attr/textColorPrimary" /> + <size android:width="8dp"/> + </shape> + </scale> + </item> +</layer-list> diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index 5411591d8df6..0a36a4fa035f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -29,17 +29,20 @@ <color name="settingslib_track_off_color">@android:color/system_neutral1_700</color> <!-- Dialog accent color --> + <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary--> <color name="settingslib_dialog_accent">@android:color/system_accent1_100</color> <!-- Dialog background color. --> <color name="settingslib_dialog_background">@color/settingslib_surface_dark</color> <!-- Dialog error color. --> <color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 --> + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color> <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color> <!-- copy from accent_primary_variant_dark_device_default--> + <!-- TODO: deprecate it after moving into partner code--> <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color> <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color> @@ -48,7 +51,9 @@ <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color> + <!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in dark theme --> <color name="settingslib_surface_dark">@android:color/system_neutral1_800</color> + <!--Deprecated. After sdk 35, don't use it--> <color name="settingslib_colorSurface">@color/settingslib_surface_dark</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml index beed90efb508..8cfe54f44fe5 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml @@ -38,7 +38,8 @@ <color name="settingslib_track_off_color">@android:color/system_surface_container_highest_dark </color> + <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface--> <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_dark</color> - + <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant--> <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_dark</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml index 229d9e330882..7c76ea1ad3b7 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml @@ -16,6 +16,34 @@ --> <resources> + <!-- Material next state on color--> + <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color> + + <!-- Material next state off color--> + <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color> + + <!-- Material next thumb disable color--> + <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color> + + <!-- Material next thumb off color--> + <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color> + + <!-- Material next thumb off color--> + <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color> + + <!-- Material next track on color--> + <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color> + + <!-- Material next track off color--> + <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> + + <!-- Dialog background color. --> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color> + + <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color> + + <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color> + <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color> <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color> <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index fe47e858b747..7706e0e8a296 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -35,49 +35,57 @@ <color name="settingslib_track_off_color">@color/settingslib_switch_track_off</color> <!-- Dialog accent color --> + <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary--> <color name="settingslib_dialog_accent">@android:color/system_accent1_600</color> <!-- Dialog background color --> <color name="settingslib_dialog_background">@color/settingslib_surface_light</color> <!-- Dialog error color. --> <color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 --> + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color> <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color> + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color> + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color> + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color> + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color> + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color> <!-- copy from accent_primary_variant_light_device_default--> + <!-- TODO: deprecate it after moving into partner code--> <color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color> - + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color> - + <!--Deprecated. After sdk 35 don't use it.using materialColorOnSurfaceInverse in dark theme--> <color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color> - + <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceInverse in light theme--> <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color> - + <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface--> <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color> - + <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant--> <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color> <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color> <color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color> - <color name="settingslib_material_grey_900">#ff212121</color> - + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_colorAccentPrimary">@color/settingslib_accent_primary_device_default</color> - + <!--Deprecated. After sdk 35 don't use it.--> <color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color> + <!--Deprecated. After sdk 35, don't use it--> <color name="settingslib_colorSurface">@color/settingslib_surface_light</color> <color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml index e4befc290939..a9534c3d5ca6 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml @@ -17,6 +17,7 @@ <resources> <style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay"> <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item> + <item name="preferenceCategoryTitleTextColor">@color/settingslib_text_color_preference_category_title</item> <item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item> <item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item> <item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml index 24e3c46b39ce..fb637fb007ae 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml @@ -16,9 +16,11 @@ --> <resources> - <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" > + <style name="Theme.SettingsBase_v33" parent="Theme.SettingsBase_v31" > <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item> <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item> <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item> </style> + + <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v33" /> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml index 3709b5d13056..185ac3e1fe73 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml @@ -39,8 +39,8 @@ <!-- Material next track outline color--> <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color> - + <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface--> <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_light</color> - + <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant--> <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_light</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml index 2691344bfdb0..2a6499aaab37 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml @@ -16,12 +16,42 @@ --> <resources> + <!-- Material next state on color--> + <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color> + + <!-- Material next state off color--> + <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color> + + <!-- Material next thumb disable color--> + <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color> + + <!-- Material next thumb off color--> + <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color> + + <!-- Material next thumb off color--> + <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color> + + <!-- Material next track on color--> + <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color> + + <!-- Material next track off color--> + <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> + + <!-- Dialog background color. --> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color> + + <!-- Material next track outline color--> + <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color> + + <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color> + + <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color> + <!-- The text color of spinner title --> <color name="settingslib_spinner_title_color">@color/settingslib_materialColorOnPrimaryContainer</color> <!-- The text color of dropdown item title --> <color name="settingslib_spinner_dropdown_color">@color/settingslib_materialColorOnPrimaryContainer</color> - <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color> <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color> <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml deleted file mode 100644 index fff41c3583f2..000000000000 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2024 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. - --> -<resources> - <style name="TextAppearance.TopIntroText" - parent="@android:style/TextAppearance.DeviceDefault"> - <item name="android:textSize">14sp</item> - <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item> - </style> - -</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml index 01dfd7d71964..cdd5c2500693 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml @@ -1,31 +1,28 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2024 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. + Copyright (C) 2024 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. --> <resources> - <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" > - <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item> - <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item> - <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item> - + <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" > <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item> - <!-- component module background --> <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item> <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item> <item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item> <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item> </style> + + <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v35" /> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt index bdc6a6890cbd..22844362f949 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt @@ -40,12 +40,13 @@ fun SettingsOutlinedTextField( singleLine: Boolean = true, enabled: Boolean = true, shape: Shape = OutlinedTextFieldDefaults.shape, + modifier: Modifier = Modifier + .fillMaxWidth() + .padding(SettingsDimension.textFieldPadding), onTextChange: (String) -> Unit ) { OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(SettingsDimension.textFieldPadding), + modifier = modifier, value = value, onValueChange = onTextChange, label = { diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java index 712f6f03a441..ea3dbd925792 100644 --- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java +++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java @@ -20,6 +20,7 @@ import android.content.Context; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; +import android.text.method.LinkMovementMethod; import android.text.style.AbsoluteSizeSpan; import android.util.AttributeSet; import android.view.View; @@ -174,6 +175,7 @@ public class UsageProgressBarPreference extends Preference { bottomSummary.setVisibility(View.GONE); } else { bottomSummary.setVisibility(View.VISIBLE); + bottomSummary.setMovementMethod(LinkMovementMethod.getInstance()); bottomSummary.setText(mBottomSummary); } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java index f73081a4eb60..169c3306c393 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java @@ -51,6 +51,7 @@ public class RecentAppOpsAccess { }; private static final int[] MICROPHONE_OPS = new int[]{ AppOpsManager.OP_RECORD_AUDIO, + AppOpsManager.OP_PHONE_CALL_MICROPHONE, }; private static final int[] CAMERA_OPS = new int[]{ AppOpsManager.OP_CAMERA, @@ -144,6 +145,11 @@ public class RecentAppOpsAccess { if (!showSystemApps) { for (int op : mOps) { final String permission = AppOpsManager.opToPermission(op); + if (permission == null) { + // Some ops like OP_PHONE_CALL_MICROPHONE don't have corresponding + // permissions. No need to check in this case. + continue; + } final int permissionFlags = mPackageManager.getPermissionFlags(permission, packageName, user); diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index 8fd4e912e04a..59e9c754a84f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.os.IDeviceIdleController; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.provider.DeviceConfig; import android.telecom.DefaultDialerManager; import android.text.TextUtils; @@ -121,6 +122,14 @@ public class PowerAllowlistBackend { return true; } + if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) { + // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy. + final int userId = UserHandle.getUserId(uid); + if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) { + return true; + } + } + return false; } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index c979d053617a..c61002ec9822 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -784,3 +784,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "slice_broadcast_relay_in_background" + namespace: "systemui" + description: "Move handling of slice broadcast relay broadcasts to background threads" + bug: "334767208" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 4533f58c1c37..356bfe23f6a5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey @@ -24,11 +25,10 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions -import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys -import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.res.R import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource @@ -75,6 +75,7 @@ fun CommunalContainer( viewModel: CommunalViewModel, dataSourceDelegator: SceneDataSourceDelegator, dialogFactory: SystemUIDialogFactory, + colors: CommunalColors, ) { val coroutineScope = rememberCoroutineScope() val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank) @@ -135,7 +136,7 @@ fun CommunalContainer( emptyMap() }, ) { - CommunalScene(viewModel, dialogFactory, modifier = modifier) + CommunalScene(viewModel, colors, dialogFactory, modifier = modifier) } } } @@ -143,15 +144,18 @@ fun CommunalContainer( /** Scene containing the glanceable hub UI. */ @Composable private fun SceneScope.CommunalScene( - viewModel: BaseCommunalViewModel, + viewModel: CommunalViewModel, + colors: CommunalColors, dialogFactory: SystemUIDialogFactory, modifier: Modifier = Modifier, ) { + val backgroundColor by colors.backgroundColor.collectAsState() + Box( modifier = Modifier.element(Communal.Elements.Scrim) .fillMaxSize() - .background(LocalAndroidColorScheme.current.outlineVariant), + .background(Color(backgroundColor.toArgb())), ) Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt index 2a99039fa306..238a230ff013 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyguard.ui.composable.modifier import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.boundsInWindow @@ -39,9 +41,12 @@ fun Modifier.burnInAware( params: BurnInParameters, isClock: Boolean = false, ): Modifier { - val burnIn = viewModel.movement(params) + val translationYState = remember { mutableStateOf(0F) } + val copiedParams = params.copy(translationY = { translationYState.value }) + val burnIn = viewModel.movement(copiedParams) val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f) val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f) + translationYState.value = translationY val scaleViewModel by burnIn .map { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index f0498ded64a5..1501d9c3505a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -51,7 +51,6 @@ class ActivityStarterImplTest : SysuiTestCase() { statusBarStateController = statusBarStateController, mainExecutor = mainExecutor, legacyActivityStarter = { legacyActivityStarterInternal }, - activityStarterInternal = { activityStarterInternal }, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt new file mode 100644 index 000000000000..55e46dc1c434 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2020 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.wmshell + +import android.content.pm.UserInfo +import android.graphics.Color +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.keyguardUpdateMonitor +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel +import com.android.systemui.communal.util.fakeCommunalColors +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.fakeDockManager +import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.ScreenLifecycle +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.wakefulnessLifecycle +import com.android.systemui.kosmos.testScope +import com.android.systemui.model.SysUiState +import com.android.systemui.model.sysUiState +import com.android.systemui.notetask.NoteTaskInitializer +import com.android.systemui.settings.FakeDisplayTracker +import com.android.systemui.settings.UserTracker +import com.android.systemui.settings.userTracker +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.commandQueue +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.configurationController +import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.wm.shell.desktopmode.DesktopMode +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener +import com.android.wm.shell.onehanded.OneHanded +import com.android.wm.shell.onehanded.OneHandedEventCallback +import com.android.wm.shell.onehanded.OneHandedTransitionCallback +import com.android.wm.shell.pip.Pip +import com.android.wm.shell.recents.RecentTasks +import com.android.wm.shell.splitscreen.SplitScreen +import com.android.wm.shell.sysui.ShellInterface +import java.util.Optional +import java.util.concurrent.Executor +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** + * Tests for [WMShell]. + * + * Build/Install/Run: atest SystemUITests:WMShellTest + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class WMShellTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + + @Mock private lateinit var mShellInterface: ShellInterface + @Mock private lateinit var mScreenLifecycle: ScreenLifecycle + @Mock private lateinit var mPip: Pip + @Mock private lateinit var mSplitScreen: SplitScreen + @Mock private lateinit var mOneHanded: OneHanded + @Mock private lateinit var mNoteTaskInitializer: NoteTaskInitializer + @Mock private lateinit var mDesktopMode: DesktopMode + @Mock private lateinit var mRecentTasks: RecentTasks + + private val mCommandQueue: CommandQueue = kosmos.commandQueue + private val mConfigurationController: ConfigurationController = kosmos.configurationController + private val mKeyguardStateController: KeyguardStateController = kosmos.keyguardStateController + private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor + private val mSysUiState: SysUiState = kosmos.sysUiState + private val mWakefulnessLifecycle: WakefulnessLifecycle = kosmos.wakefulnessLifecycle + private val mUserTracker: UserTracker = kosmos.userTracker + private val mSysUiMainExecutor: Executor = kosmos.fakeExecutor + private val communalTransitionViewModel = kosmos.communalTransitionViewModel + + private lateinit var underTest: WMShell + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + val displayTracker = FakeDisplayTracker(mContext) + + kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + + underTest = + WMShell( + mContext, + mShellInterface, + Optional.of(mPip), + Optional.of(mSplitScreen), + Optional.of(mOneHanded), + Optional.of(mDesktopMode), + Optional.of(mRecentTasks), + mCommandQueue, + mConfigurationController, + mKeyguardStateController, + mKeyguardUpdateMonitor, + mScreenLifecycle, + mSysUiState, + mWakefulnessLifecycle, + mUserTracker, + displayTracker, + mNoteTaskInitializer, + communalTransitionViewModel, + JavaAdapter(testScope.backgroundScope), + mSysUiMainExecutor + ) + } + + @Test + fun initPip_registersCommandQueueCallback() { + underTest.initPip(mPip) + verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java)) + } + + @Test + fun initOneHanded_registersCallbacks() { + underTest.initOneHanded(mOneHanded) + verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java)) + verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer::class.java)) + verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback::class.java)) + verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback::class.java)) + } + + @Test + fun initDesktopMode_registersListener() { + underTest.initDesktopMode(mDesktopMode) + verify(mDesktopMode) + .addVisibleTasksListener( + any(VisibleTasksListener::class.java), + any(Executor::class.java) + ) + } + + @Test + fun initRecentTasks_registersListener() { + underTest.initRecentTasks(mRecentTasks) + verify(mRecentTasks).addAnimationStateListener(any(Executor::class.java), any()) + } + + @Test + @EnableFlags(FLAG_COMMUNAL_HUB) + fun initRecentTasks_setRecentsBackgroundColorWhenCommunal() = + testScope.runTest { + val black = Color.valueOf(Color.BLACK) + kosmos.fakeCommunalColors.setBackgroundColor(black) + + kosmos.fakeKeyguardRepository.setKeyguardShowing(false) + + underTest.initRecentTasks(mRecentTasks) + runCurrent() + verify(mRecentTasks).setTransitionBackgroundColor(null) + verify(mRecentTasks, never()).setTransitionBackgroundColor(black) + + setDocked(true) + // Make communal available + kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) + kosmos.fakeKeyguardRepository.setKeyguardShowing(true) + + runCurrent() + + verify(mRecentTasks).setTransitionBackgroundColor(black) + } + + private fun TestScope.setDocked(docked: Boolean) { + kosmos.fakeDockManager.setIsDocked(docked) + val event = + if (docked) { + DockManager.STATE_DOCKED + } else { + DockManager.STATE_NONE + } + kosmos.fakeDockManager.setDockEvent(event) + runCurrent() + } + + private companion object { + val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java index 5bd85a72b06f..429d3f09fa6a 100644 --- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -14,6 +14,8 @@ package com.android.systemui; +import static com.android.systemui.Flags.sliceBroadcastRelayInBackground; + import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -23,13 +25,19 @@ import android.content.IntentFilter; import android.net.Uri; import android.os.UserHandle; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; +import androidx.annotation.GuardedBy; +import androidx.annotation.WorkerThread; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.SliceBroadcastRelay; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; + +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -42,14 +50,18 @@ public class SliceBroadcastRelayHandler implements CoreStartable { private static final String TAG = "SliceBroadcastRelay"; private static final boolean DEBUG = false; + @GuardedBy("mRelays") private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>(); private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; + private final Executor mBackgroundExecutor; @Inject - public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher) { + public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher, + @Background Executor backgroundExecutor) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; + mBackgroundExecutor = backgroundExecutor; } @Override @@ -57,21 +69,29 @@ public class SliceBroadcastRelayHandler implements CoreStartable { if (DEBUG) Log.d(TAG, "Start"); IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER); filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER); - mBroadcastDispatcher.registerReceiver(mReceiver, filter); + + if (sliceBroadcastRelayInBackground()) { + mBroadcastDispatcher.registerReceiver(mReceiver, filter, mBackgroundExecutor); + } else { + mBroadcastDispatcher.registerReceiver(mReceiver, filter); + } } // This does not use BroadcastDispatcher as the filter may have schemas or mime types. + @WorkerThread @VisibleForTesting void handleIntent(Intent intent) { if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) { - Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI); + Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class); ComponentName receiverClass = - intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER); - IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER); + intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER, + ComponentName.class); + IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER, + IntentFilter.class); if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter); getOrCreateRelay(uri).register(mContext, receiverClass, filter); } else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) { - Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI); + Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class); if (DEBUG) Log.d(TAG, "Unregister " + uri); BroadcastRelay relay = getAndRemoveRelay(uri); if (relay != null) { @@ -80,17 +100,23 @@ public class SliceBroadcastRelayHandler implements CoreStartable { } } + @WorkerThread private BroadcastRelay getOrCreateRelay(Uri uri) { - BroadcastRelay ret = mRelays.get(uri); - if (ret == null) { - ret = new BroadcastRelay(uri); - mRelays.put(uri, ret); + synchronized (mRelays) { + BroadcastRelay ret = mRelays.get(uri); + if (ret == null) { + ret = new BroadcastRelay(uri); + mRelays.put(uri, ret); + } + return ret; } - return ret; } + @WorkerThread private BroadcastRelay getAndRemoveRelay(Uri uri) { - return mRelays.remove(uri); + synchronized (mRelays) { + return mRelays.remove(uri); + } } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -102,7 +128,7 @@ public class SliceBroadcastRelayHandler implements CoreStartable { private static class BroadcastRelay extends BroadcastReceiver { - private final ArraySet<ComponentName> mReceivers = new ArraySet<>(); + private final CopyOnWriteArraySet<ComponentName> mReceivers = new CopyOnWriteArraySet<>(); private final UserHandle mUserId; private final Uri mUri; diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 72dcb26b089a..27af99ef8014 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -24,6 +24,8 @@ import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryM import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.util.CommunalColors +import com.android.systemui.communal.util.CommunalColorsImpl import com.android.systemui.communal.widgets.CommunalWidgetModule import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl @@ -60,6 +62,8 @@ interface CommunalModule { @Communal fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource + @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors + companion object { @Provides @Communal diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index bdf4e721a551..1bee83b41dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -16,7 +16,9 @@ package com.android.systemui.communal.ui.viewmodel +import android.graphics.Color import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -28,6 +30,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTrans import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.merge @@ -38,6 +41,7 @@ import kotlinx.coroutines.flow.merge class CommunalTransitionViewModel @Inject constructor( + communalColors: CommunalColors, glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, @@ -68,4 +72,13 @@ constructor( step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } + + val recentsBackgroundColor: Flow<Color?> = + combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor -> + if (showByDefault) { + backgroundColor + } else { + null + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt new file mode 100644 index 000000000000..1e04fe7b6eb0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 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.communal.util + +import android.content.Context +import android.graphics.Color +import com.android.settingslib.Utils +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** Wrapper around colors used for the communal UI. */ +interface CommunalColors { + /** The background color of the glanceable hub. */ + val backgroundColor: StateFlow<Color> +} + +@SysUISingleton +class CommunalColorsImpl +@Inject +constructor( + @Application applicationScope: CoroutineScope, + private val context: Context, + configurationInteractor: ConfigurationInteractor, +) : CommunalColors { + override val backgroundColor: StateFlow<Color> = + configurationInteractor.onAnyConfigurationChange + .map { loadBackgroundColor() } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = loadBackgroundColor() + ) + + private fun loadBackgroundColor(): Color = + Color.valueOf( + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorOutlineVariant + ) + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index db6b8fea9235..9ada1ef00bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -367,16 +367,6 @@ object Flags { val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK = sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false) - // TODO(b/254512728): Tracking Bug - @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance") - - - // TODO(b/270987164): Tracking Bug - @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features") - - // TODO(b/273800936): Tracking Bug - @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common") - // TODO(b/251205791): Tracking Bug @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips") diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index b6098645f7bc..43c73c4e7f35 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -411,18 +411,16 @@ public class NavigationBar extends ViewController<NavigationBarView> implements @Override public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) { + Log.d(TAG, "setOverrideHomeButtonLongPress receives: " + duration + "; " + + slopMultiplier); mOverrideHomeButtonLongPressDurationMs = Optional.of(duration) .filter(value -> value > 0); mOverrideHomeButtonLongPressSlopMultiplier = Optional.of(slopMultiplier) .filter(value -> value > 0); - if (mOverrideHomeButtonLongPressDurationMs.isPresent()) { - Log.d(TAG, "Receive duration override: " - + mOverrideHomeButtonLongPressDurationMs.get()); - } - if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) { - Log.d(TAG, "Receive slop multiplier override: " - + mOverrideHomeButtonLongPressSlopMultiplier.get()); - } + mOverrideHomeButtonLongPressDurationMs.ifPresent(aLong + -> Log.d(TAG, "Use duration override: " + aLong)); + mOverrideHomeButtonLongPressSlopMultiplier.ifPresent(aFloat + -> Log.d(TAG, "Use slop multiplier override: " + aFloat)); if (mView != null) { reconfigureHomeLongClick(); } @@ -1395,9 +1393,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements break; case MotionEvent.ACTION_MOVE: if (!mHandler.hasCallbacks(mOnVariableDurationHomeLongClick)) { - Log.w(TAG, "No callback. Don't handle touch slop."); + Log.v(TAG, "ACTION_MOVE no callback. Don't handle touch slop."); break; } + Log.v(TAG, "ACTION_MOVE handle touch slop"); float customSlopMultiplier = mOverrideHomeButtonLongPressSlopMultiplier.orElse(1f); float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); float calculatedTouchSlop = diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index db4a7fad579c..b50ee576a4c3 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -71,8 +71,6 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; @@ -219,10 +217,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final Region mExcludeRegion = new Region(); private final Region mDesktopModeExcludeRegion = new Region(); private final Region mUnrestrictedExcludeRegion = new Region(); - private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider; private final Provider<BackGestureTfClassifierProvider> mBackGestureTfClassifierProviderProvider; - private final FeatureFlags mFeatureFlags; private final Provider<LightBarController> mLightBarControllerProvider; // The left side edge width where touch down is allowed @@ -264,8 +260,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; private boolean mIsBackGestureAllowed; - private boolean mIsNewBackAffordanceEnabled; - private boolean mIsTrackpadGestureFeaturesEnabled; private boolean mIsTrackpadThreeFingerSwipe; private boolean mIsButtonForcedVisible; @@ -413,9 +407,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Optional<Pip> pipOptional, Optional<DesktopMode> desktopModeOptional, FalsingManager falsingManager, - Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider, Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider, - FeatureFlags featureFlags, Provider<LightBarController> lightBarControllerProvider) { mContext = context; mDisplayId = context.getDisplayId(); @@ -435,13 +427,9 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mPipOptional = pipOptional; mDesktopModeOptional = desktopModeOptional; mFalsingManager = falsingManager; - mNavBarEdgePanelProvider = navigationBarEdgePanelProvider; mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider; - mFeatureFlags = featureFlags; mLightBarControllerProvider = lightBarControllerProvider; mLastReportedConfig.setTo(mContext.getResources().getConfiguration()); - mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled( - Flags.TRACKPAD_GESTURE_FEATURES); ComponentName recentsComponentName = ComponentName.unflattenFromString( context.getString(com.android.internal.R.string.config_recentsComponentName)); if (recentsComponentName != null) { @@ -559,12 +547,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mIsAttached = true; mOverviewProxyService.addCallback(mQuickSwitchListener); mSysUiState.addCallback(mSysUiStateCallback); - if (mIsTrackpadGestureFeaturesEnabled) { - mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler); - int [] inputDevices = mInputManager.getInputDeviceIds(); - for (int inputDeviceId : inputDevices) { - mInputDeviceListener.onInputDeviceAdded(inputDeviceId); - } + mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler); + int [] inputDevices = mInputManager.getInputDeviceIds(); + for (int inputDeviceId : inputDevices) { + mInputDeviceListener.onInputDeviceAdded(inputDeviceId); } updateIsEnabled(); mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); @@ -616,9 +602,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack try { Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled"); - mIsGestureHandlingEnabled = - mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav - && mIsTrackpadConnected); + mIsGestureHandlingEnabled = mInGestureNavMode || (mUsingThreeButtonNav + && mIsTrackpadConnected); boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled; if (isEnabled == mIsEnabled) { return; @@ -678,7 +663,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Choreographer.getInstance(), this::onInputEvent); // Add a nav bar panel window - mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE); resetEdgeBackPlugin(); mPluginManager.addPluginListener( this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); @@ -701,12 +685,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } private void resetEdgeBackPlugin() { - if (mIsNewBackAffordanceEnabled) { - setEdgeBackPlugin( - mBackPanelControllerFactory.create(mContext)); - } else { - setEdgeBackPlugin(mNavBarEdgePanelProvider.get()); - } + setEdgeBackPlugin(mBackPanelControllerFactory.create(mContext)); } private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { @@ -1001,8 +980,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev); } - mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe( - mIsTrackpadGestureFeaturesEnabled, ev); + mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(ev); // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new // ACTION_DOWN, in that case we should just reuse the old instance. @@ -1027,7 +1005,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack && !mGestureBlockingActivityRunning.get() && !QuickStepContract.isBackGestureDisabled(mSysUiFlags, mIsTrackpadThreeFingerSwipe) - && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev); + && !isTrackpadScroll(ev); if (mIsTrackpadThreeFingerSwipe) { // Trackpad back gestures don't have zones, so we don't need to check if the down // event is within insets. @@ -1321,10 +1299,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final Optional<Pip> mPipOptional; private final Optional<DesktopMode> mDesktopModeOptional; private final FalsingManager mFalsingManager; - private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider; private final Provider<BackGestureTfClassifierProvider> mBackGestureTfClassifierProviderProvider; - private final FeatureFlags mFeatureFlags; private final Provider<LightBarController> mLightBarControllerProvider; @Inject @@ -1344,10 +1320,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Optional<Pip> pipOptional, Optional<DesktopMode> desktopModeOptional, FalsingManager falsingManager, - Provider<NavigationBarEdgePanel> navBarEdgePanelProvider, Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider, - FeatureFlags featureFlags, Provider<LightBarController> lightBarControllerProvider) { mOverviewProxyService = overviewProxyService; mSysUiState = sysUiState; @@ -1365,9 +1339,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mPipOptional = pipOptional; mDesktopModeOptional = desktopModeOptional; mFalsingManager = falsingManager; - mNavBarEdgePanelProvider = navBarEdgePanelProvider; mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider; - mFeatureFlags = featureFlags; mLightBarControllerProvider = lightBarControllerProvider; } @@ -1391,9 +1363,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mPipOptional, mDesktopModeOptional, mFalsingManager, - mNavBarEdgePanelProvider, mBackGestureTfClassifierProviderProvider, - mFeatureFlags, mLightBarControllerProvider); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java deleted file mode 100644 index 380846e17a13..000000000000 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ /dev/null @@ -1,944 +0,0 @@ -/* - * Copyright (C) 2020 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.navigationbar.gestural; - -import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE; -import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Handler; -import android.os.SystemClock; -import android.os.VibrationEffect; -import android.util.Log; -import android.util.MathUtils; -import android.view.ContextThemeWrapper; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.WindowManager; -import android.view.animation.Interpolator; -import android.view.animation.PathInterpolator; - -import androidx.core.graphics.ColorUtils; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.FloatPropertyCompat; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - -import com.android.app.animation.Interpolators; -import com.android.internal.util.LatencyTracker; -import com.android.settingslib.Utils; -import com.android.systemui.res.R; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.plugins.NavigationEdgeBackPlugin; -import com.android.systemui.settings.DisplayTracker; -import com.android.systemui.shared.navigationbar.RegionSamplingHelper; -import com.android.systemui.statusbar.VibratorHelper; - -import java.io.PrintWriter; -import java.util.concurrent.Executor; - -import javax.inject.Inject; - -public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin { - - private static final String TAG = "NavigationBarEdgePanel"; - - private static final boolean ENABLE_FAILSAFE = true; - - private static final long COLOR_ANIMATION_DURATION_MS = 120; - private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80; - private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100; - private static final long FAILSAFE_DELAY_MS = 200; - - /** - * The time required since the first vibration effect to automatically trigger a click - */ - private static final int GESTURE_DURATION_FOR_CLICK_MS = 400; - - /** - * The size of the protection of the arrow in px. Only used if this is not background protected - */ - private static final int PROTECTION_WIDTH_PX = 2; - - /** - * The basic translation in dp where the arrow resides - */ - private static final int BASE_TRANSLATION_DP = 32; - - /** - * The length of the arrow leg measured from the center to the end - */ - private static final int ARROW_LENGTH_DP = 18; - - /** - * The angle measured from the xAxis, where the leg is when the arrow rests - */ - private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56; - - /** - * The angle that is added per 1000 px speed to the angle of the leg - */ - private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4; - - /** - * The maximum angle offset allowed due to speed - */ - private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4; - - /** - * The thickness of the arrow. Adjusted to match the home handle (approximately) - */ - private static final float ARROW_THICKNESS_DP = 2.5f; - - /** - * The amount of rubber banding we do for the vertical translation - */ - private static final int RUBBER_BAND_AMOUNT = 15; - - /** - * The interpolator used to rubberband - */ - private static final Interpolator RUBBER_BAND_INTERPOLATOR - = new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f); - - /** - * The amount of rubber banding we do for the translation before base translation - */ - private static final int RUBBER_BAND_AMOUNT_APPEAR = 4; - - /** - * The interpolator used to rubberband the appearing of the arrow. - */ - private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR - = new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f); - - private final WindowManager mWindowManager; - private final VibratorHelper mVibratorHelper; - - /** - * The paint the arrow is drawn with - */ - private final Paint mPaint = new Paint(); - /** - * The paint the arrow protection is drawn with - */ - private final Paint mProtectionPaint; - - private final float mDensity; - private final float mBaseTranslation; - private final float mArrowLength; - private final float mArrowThickness; - - /** - * The minimum delta needed in movement for the arrow to change direction / stop triggering back - */ - private final float mMinDeltaForSwitch; - // The closest to y = 0 that the arrow will be displayed. - private int mMinArrowPosition; - // The amount the arrow is shifted to avoid the finger. - private int mFingerOffset; - - private final float mSwipeTriggerThreshold; - private final float mSwipeProgressThreshold; - private final Path mArrowPath = new Path(); - private final Point mDisplaySize = new Point(); - - private final SpringAnimation mAngleAnimation; - private final SpringAnimation mTranslationAnimation; - private final SpringAnimation mVerticalTranslationAnimation; - private final SpringForce mAngleAppearForce; - private final SpringForce mAngleDisappearForce; - private final ValueAnimator mArrowColorAnimator; - private final ValueAnimator mArrowDisappearAnimation; - private final SpringForce mRegularTranslationSpring; - private final SpringForce mTriggerBackSpring; - private final LatencyTracker mLatencyTracker; - - private VelocityTracker mVelocityTracker; - private boolean mIsDark = false; - private boolean mShowProtection = false; - private int mProtectionColorLight; - private int mArrowPaddingEnd; - private int mArrowColorLight; - private int mProtectionColorDark; - private int mArrowColorDark; - private int mProtectionColor; - private int mArrowColor; - private RegionSamplingHelper mRegionSamplingHelper; - private final Rect mSamplingRect = new Rect(); - private WindowManager.LayoutParams mLayoutParams; - private int mLeftInset; - private int mRightInset; - - /** - * True if the panel is currently on the left of the screen - */ - private boolean mIsLeftPanel; - - private float mStartX; - private float mStartY; - private float mCurrentAngle; - /** - * The current translation of the arrow - */ - private float mCurrentTranslation; - /** - * Where the arrow will be in the resting position. - */ - private float mDesiredTranslation; - - private boolean mDragSlopPassed; - private boolean mArrowsPointLeft; - private float mMaxTranslation; - private boolean mTriggerBack; - private float mPreviousTouchTranslation; - private float mTotalTouchDelta; - private float mVerticalTranslation; - private float mDesiredVerticalTranslation; - private float mDesiredAngle; - private float mAngleOffset; - private int mArrowStartColor; - private int mCurrentArrowColor; - private float mDisappearAmount; - private long mVibrationTime; - private int mScreenSize; - private boolean mTrackingBackArrowLatency = false; - - private final Handler mHandler = new Handler(); - private final Runnable mFailsafeRunnable = this::onFailsafe; - - private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener - = new DynamicAnimation.OnAnimationEndListener() { - @Override - public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, - float velocity) { - animation.removeEndListener(this); - if (!canceled) { - setVisibility(GONE); - } - } - }; - private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_ANGLE = - new FloatPropertyCompat<NavigationBarEdgePanel>("currentAngle") { - @Override - public void setValue(NavigationBarEdgePanel object, float value) { - object.setCurrentAngle(value); - } - - @Override - public float getValue(NavigationBarEdgePanel object) { - return object.getCurrentAngle(); - } - }; - - private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_TRANSLATION = - new FloatPropertyCompat<NavigationBarEdgePanel>("currentTranslation") { - - @Override - public void setValue(NavigationBarEdgePanel object, float value) { - object.setCurrentTranslation(value); - } - - @Override - public float getValue(NavigationBarEdgePanel object) { - return object.getCurrentTranslation(); - } - }; - private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_VERTICAL_TRANSLATION = - new FloatPropertyCompat<NavigationBarEdgePanel>("verticalTranslation") { - - @Override - public void setValue(NavigationBarEdgePanel object, float value) { - object.setVerticalTranslation(value); - } - - @Override - public float getValue(NavigationBarEdgePanel object) { - return object.getVerticalTranslation(); - } - }; - private BackCallback mBackCallback; - - @Inject - public NavigationBarEdgePanel( - Context context, - LatencyTracker latencyTracker, - VibratorHelper vibratorHelper, - @Background Executor backgroundExecutor, - DisplayTracker displayTracker) { - super(context); - - mWindowManager = context.getSystemService(WindowManager.class); - mVibratorHelper = vibratorHelper; - - mDensity = context.getResources().getDisplayMetrics().density; - - mBaseTranslation = dp(BASE_TRANSLATION_DP); - mArrowLength = dp(ARROW_LENGTH_DP); - mArrowThickness = dp(ARROW_THICKNESS_DP); - mMinDeltaForSwitch = dp(32); - - mPaint.setStrokeWidth(mArrowThickness); - mPaint.setStrokeCap(Paint.Cap.ROUND); - mPaint.setAntiAlias(true); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeJoin(Paint.Join.ROUND); - - mArrowColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mArrowColorAnimator.setDuration(COLOR_ANIMATION_DURATION_MS); - mArrowColorAnimator.addUpdateListener(animation -> { - int newColor = ColorUtils.blendARGB( - mArrowStartColor, mArrowColor, animation.getAnimatedFraction()); - setCurrentArrowColor(newColor); - }); - - mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); - mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS); - mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mArrowDisappearAnimation.addUpdateListener(animation -> { - mDisappearAmount = (float) animation.getAnimatedValue(); - invalidate(); - }); - - mAngleAnimation = - new SpringAnimation(this, CURRENT_ANGLE); - mAngleAppearForce = new SpringForce() - .setStiffness(500) - .setDampingRatio(0.5f); - mAngleDisappearForce = new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY) - .setFinalPosition(90); - mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90); - - mTranslationAnimation = - new SpringAnimation(this, CURRENT_TRANSLATION); - mRegularTranslationSpring = new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); - mTriggerBackSpring = new SpringForce() - .setStiffness(450) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); - mTranslationAnimation.setSpring(mRegularTranslationSpring); - mVerticalTranslationAnimation = - new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION); - mVerticalTranslationAnimation.setSpring( - new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - - mProtectionPaint = new Paint(mPaint); - mProtectionPaint.setStrokeWidth(mArrowThickness + PROTECTION_WIDTH_PX); - loadDimens(); - - loadColors(context); - updateArrowDirection(); - - mSwipeTriggerThreshold = context.getResources() - .getDimension(R.dimen.navigation_edge_action_drag_threshold); - mSwipeProgressThreshold = context.getResources() - .getDimension(R.dimen.navigation_edge_action_progress_threshold); - - setVisibility(GONE); - - boolean isPrimaryDisplay = mContext.getDisplayId() == displayTracker.getDefaultDisplayId(); - mRegionSamplingHelper = new RegionSamplingHelper(this, - new RegionSamplingHelper.SamplingCallback() { - @Override - public void onRegionDarknessChanged(boolean isRegionDark) { - setIsDark(!isRegionDark, true /* animate */); - } - - @Override - public Rect getSampledRegion(View sampledView) { - return mSamplingRect; - } - - @Override - public boolean isSamplingEnabled() { - return isPrimaryDisplay; - } - }, backgroundExecutor); - mRegionSamplingHelper.setWindowVisible(true); - mShowProtection = !isPrimaryDisplay; - mLatencyTracker = latencyTracker; - } - - @Override - public void onDestroy() { - cancelFailsafe(); - mWindowManager.removeView(this); - mRegionSamplingHelper.stop(); - mRegionSamplingHelper = null; - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - private void setIsDark(boolean isDark, boolean animate) { - mIsDark = isDark; - updateIsDark(animate); - } - - @Override - public void setIsLeftPanel(boolean isLeftPanel) { - mIsLeftPanel = isLeftPanel; - mLayoutParams.gravity = mIsLeftPanel - ? (Gravity.LEFT | Gravity.TOP) - : (Gravity.RIGHT | Gravity.TOP); - } - - @Override - public void setInsets(int leftInset, int rightInset) { - mLeftInset = leftInset; - mRightInset = rightInset; - } - - @Override - public void setDisplaySize(Point displaySize) { - mDisplaySize.set(displaySize.x, displaySize.y); - mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y); - } - - @Override - public void setBackCallback(BackCallback callback) { - mBackCallback = callback; - } - - @Override - public void setLayoutParams(WindowManager.LayoutParams layoutParams) { - mLayoutParams = layoutParams; - mWindowManager.addView(this, mLayoutParams); - } - - /** - * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow. - */ - private void adjustSamplingRectToBoundingBox() { - float translation = mDesiredTranslation; - if (!mTriggerBack) { - // Let's take the resting position and bounds as the sampling rect, since we are not - // visible right now - translation = mBaseTranslation; - if (mIsLeftPanel && mArrowsPointLeft - || (!mIsLeftPanel && !mArrowsPointLeft)) { - // If we're on the left we should move less, because the arrow is facing the other - // direction - translation -= getStaticArrowWidth(); - } - } - float left = translation - mArrowThickness / 2.0f; - left = mIsLeftPanel ? left : mSamplingRect.width() - left; - - // Let's calculate the position of the end based on the angle - float width = getStaticArrowWidth(); - float height = polarToCartY(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength * 2.0f; - if (!mArrowsPointLeft) { - left -= width; - } - - float top = (getHeight() * 0.5f) + mDesiredVerticalTranslation - height / 2.0f; - mSamplingRect.offset((int) left, (int) top); - mSamplingRect.set(mSamplingRect.left, mSamplingRect.top, - (int) (mSamplingRect.left + width), - (int) (mSamplingRect.top + height)); - mRegionSamplingHelper.updateSamplingRect(); - } - - @Override - public void onMotionEvent(MotionEvent event) { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mDragSlopPassed = false; - resetOnDown(); - mStartX = event.getX(); - mStartY = event.getY(); - setVisibility(VISIBLE); - updatePosition(event.getY()); - mRegionSamplingHelper.start(mSamplingRect); - mWindowManager.updateViewLayout(this, mLayoutParams); - mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW); - mTrackingBackArrowLatency = true; - break; - case MotionEvent.ACTION_MOVE: - handleMoveEvent(event); - break; - case MotionEvent.ACTION_UP: - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, - "NavigationBarEdgePanel ACTION_UP, mTriggerBack=" + mTriggerBack); - } - if (mTriggerBack) { - triggerBack(); - } else { - cancelBack(); - } - mRegionSamplingHelper.stop(); - mVelocityTracker.recycle(); - mVelocityTracker = null; - break; - case MotionEvent.ACTION_CANCEL: - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "NavigationBarEdgePanel ACTION_CANCEL"); - } - cancelBack(); - mRegionSamplingHelper.stop(); - mVelocityTracker.recycle(); - mVelocityTracker = null; - break; - } - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateArrowDirection(); - loadDimens(); - } - - @Override - protected void onDraw(Canvas canvas) { - float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f; - canvas.save(); - canvas.translate( - mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition, - (getHeight() * 0.5f) + mVerticalTranslation); - - // Let's calculate the position of the end based on the angle - float x = (polarToCartX(mCurrentAngle) * mArrowLength); - float y = (polarToCartY(mCurrentAngle) * mArrowLength); - Path arrowPath = calculatePath(x,y); - if (mShowProtection) { - canvas.drawPath(arrowPath, mProtectionPaint); - } - - canvas.drawPath(arrowPath, mPaint); - canvas.restore(); - if (mTrackingBackArrowLatency) { - mLatencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW); - mTrackingBackArrowLatency = false; - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - mMaxTranslation = getWidth() - mArrowPaddingEnd; - } - - private void loadDimens() { - Resources res = getResources(); - mArrowPaddingEnd = res.getDimensionPixelSize(R.dimen.navigation_edge_panel_padding); - mMinArrowPosition = res.getDimensionPixelSize(R.dimen.navigation_edge_arrow_min_y); - mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset); - } - - private void updateArrowDirection() { - // Both panels arrow point the same way - mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR; - invalidate(); - } - - private void loadColors(Context context) { - final int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme); - final int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme); - Context lightContext = new ContextThemeWrapper(context, dualToneLightTheme); - Context darkContext = new ContextThemeWrapper(context, dualToneDarkTheme); - mArrowColorLight = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor); - mArrowColorDark = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor); - mProtectionColorDark = mArrowColorLight; - mProtectionColorLight = mArrowColorDark; - updateIsDark(false /* animate */); - } - - private void updateIsDark(boolean animate) { - // TODO: Maybe animate protection as well - mProtectionColor = mIsDark ? mProtectionColorDark : mProtectionColorLight; - mProtectionPaint.setColor(mProtectionColor); - mArrowColor = mIsDark ? mArrowColorDark : mArrowColorLight; - mArrowColorAnimator.cancel(); - if (!animate) { - setCurrentArrowColor(mArrowColor); - } else { - mArrowStartColor = mCurrentArrowColor; - mArrowColorAnimator.start(); - } - } - - private void setCurrentArrowColor(int color) { - mCurrentArrowColor = color; - mPaint.setColor(color); - invalidate(); - } - - private float getStaticArrowWidth() { - return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength; - } - - private float polarToCartX(float angleInDegrees) { - return (float) Math.cos(Math.toRadians(angleInDegrees)); - } - - private float polarToCartY(float angleInDegrees) { - return (float) Math.sin(Math.toRadians(angleInDegrees)); - } - - private Path calculatePath(float x, float y) { - if (!mArrowsPointLeft) { - x = -x; - } - float extent = MathUtils.lerp(1.0f, 0.75f, mDisappearAmount); - x = x * extent; - y = y * extent; - mArrowPath.reset(); - mArrowPath.moveTo(x, y); - mArrowPath.lineTo(0, 0); - mArrowPath.lineTo(x, -y); - return mArrowPath; - } - - private float getCurrentAngle() { - return mCurrentAngle; - } - - private float getCurrentTranslation() { - return mCurrentTranslation; - } - - private void triggerBack() { - mBackCallback.triggerBack(); - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.computeCurrentVelocity(1000); - // Only do the extra translation if we're not already flinging - boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500; - if (isSlow - || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) { - mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK); - } - - // Let's also snap the angle a bit - if (mAngleOffset > -4) { - mAngleOffset = Math.max(-8, mAngleOffset - 8); - updateAngle(true /* animated */); - } - - // Finally, after the translation, animate back and disappear the arrow - Runnable translationEnd = () -> { - // let's snap it back - mAngleOffset = Math.max(0, mAngleOffset + 8); - updateAngle(true /* animated */); - - mTranslationAnimation.setSpring(mTriggerBackSpring); - // Translate the arrow back a bit to make for a nice transition - setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */); - animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS) - .withEndAction(() -> setVisibility(GONE)); - mArrowDisappearAnimation.start(); - // Schedule failsafe in case alpha end callback is not called - scheduleFailsafe(); - }; - if (mTranslationAnimation.isRunning()) { - mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() { - @Override - public void onAnimationEnd(DynamicAnimation animation, boolean canceled, - float value, - float velocity) { - animation.removeEndListener(this); - if (!canceled) { - translationEnd.run(); - } - } - }); - // Schedule failsafe in case mTranslationAnimation end callback is not called - scheduleFailsafe(); - } else { - translationEnd.run(); - } - } - - private void cancelBack() { - mBackCallback.cancelBack(); - - if (mTranslationAnimation.isRunning()) { - mTranslationAnimation.addEndListener(mSetGoneEndListener); - // Schedule failsafe in case mTranslationAnimation end callback is not called - scheduleFailsafe(); - } else { - setVisibility(GONE); - } - } - - private void resetOnDown() { - animate().cancel(); - mAngleAnimation.cancel(); - mTranslationAnimation.cancel(); - mVerticalTranslationAnimation.cancel(); - mArrowDisappearAnimation.cancel(); - mAngleOffset = 0; - mTranslationAnimation.setSpring(mRegularTranslationSpring); - // Reset the arrow to the side - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "reset mTriggerBack=false"); - } - setTriggerBack(false /* triggerBack */, false /* animated */); - setDesiredTranslation(0, false /* animated */); - setCurrentTranslation(0); - updateAngle(false /* animate */); - mPreviousTouchTranslation = 0; - mTotalTouchDelta = 0; - mVibrationTime = 0; - setDesiredVerticalTransition(0, false /* animated */); - cancelFailsafe(); - } - - private void handleMoveEvent(MotionEvent event) { - float x = event.getX(); - float y = event.getY(); - float touchTranslation = MathUtils.abs(x - mStartX); - float yOffset = y - mStartY; - float delta = touchTranslation - mPreviousTouchTranslation; - if (Math.abs(delta) > 0) { - if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) { - mTotalTouchDelta += delta; - } else { - mTotalTouchDelta = delta; - } - } - mPreviousTouchTranslation = touchTranslation; - - // Apply a haptic on drag slop passed - if (!mDragSlopPassed && touchTranslation > mSwipeTriggerThreshold) { - mDragSlopPassed = true; - mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); - mVibrationTime = SystemClock.uptimeMillis(); - - // Let's show the arrow and animate it in! - mDisappearAmount = 0.0f; - setAlpha(1f); - // And animate it go to back by default! - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=true"); - } - setTriggerBack(true /* triggerBack */, true /* animated */); - } - - // Let's make sure we only go to the baseextend and apply rubberbanding afterwards - if (touchTranslation > mBaseTranslation) { - float diff = touchTranslation - mBaseTranslation; - float progress = MathUtils.saturate(diff / (mScreenSize - mBaseTranslation)); - progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress) - * (mMaxTranslation - mBaseTranslation); - touchTranslation = mBaseTranslation + progress; - } else { - float diff = mBaseTranslation - touchTranslation; - float progress = MathUtils.saturate(diff / mBaseTranslation); - progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress) - * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR); - touchTranslation = mBaseTranslation - progress; - } - // By default we just assume the current direction is kept - boolean triggerBack = mTriggerBack; - - // First lets see if we had continuous motion in one direction for a while - if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) { - triggerBack = mTotalTouchDelta > 0; - } - - // Then, let's see if our velocity tells us to change direction - mVelocityTracker.computeCurrentVelocity(1000); - float xVelocity = mVelocityTracker.getXVelocity(); - float yVelocity = mVelocityTracker.getYVelocity(); - float velocity = MathUtils.mag(xVelocity, yVelocity); - mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED, - ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity); - if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) { - mAngleOffset *= -1; - } - - // Last if the direction in Y is bigger than X * 2 we also abort - if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) { - triggerBack = false; - } - if (DEBUG_MISSING_GESTURE && mTriggerBack != triggerBack) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=" + triggerBack - + ", mTotalTouchDelta=" + mTotalTouchDelta - + ", mMinDeltaForSwitch=" + mMinDeltaForSwitch - + ", yOffset=" + yOffset - + ", x=" + x - + ", mStartX=" + mStartX); - } - setTriggerBack(triggerBack, true /* animated */); - - if (!mTriggerBack) { - touchTranslation = 0; - } else if (mIsLeftPanel && mArrowsPointLeft - || (!mIsLeftPanel && !mArrowsPointLeft)) { - // If we're on the left we should move less, because the arrow is facing the other - // direction - touchTranslation -= getStaticArrowWidth(); - } - setDesiredTranslation(touchTranslation, true /* animated */); - updateAngle(true /* animated */); - - float maxYOffset = getHeight() / 2.0f - mArrowLength; - float progress = MathUtils.constrain( - Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT), - 0, 1); - float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress) - * maxYOffset * Math.signum(yOffset); - setDesiredVerticalTransition(verticalTranslation, true /* animated */); - updateSamplingRect(); - } - - private void updatePosition(float touchY) { - float position = touchY - mFingerOffset; - position = Math.max(position, mMinArrowPosition); - position -= mLayoutParams.height / 2.0f; - mLayoutParams.y = MathUtils.constrain((int) position, 0, mDisplaySize.y); - updateSamplingRect(); - } - - private void updateSamplingRect() { - int top = mLayoutParams.y; - int left = mIsLeftPanel ? mLeftInset : mDisplaySize.x - mRightInset - mLayoutParams.width; - int right = left + mLayoutParams.width; - int bottom = top + mLayoutParams.height; - mSamplingRect.set(left, top, right, bottom); - adjustSamplingRectToBoundingBox(); - } - - private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) { - if (mDesiredVerticalTranslation != verticalTranslation) { - mDesiredVerticalTranslation = verticalTranslation; - if (!animated) { - setVerticalTranslation(verticalTranslation); - } else { - mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation); - } - invalidate(); - } - } - - private void setVerticalTranslation(float verticalTranslation) { - mVerticalTranslation = verticalTranslation; - invalidate(); - } - - private float getVerticalTranslation() { - return mVerticalTranslation; - } - - private void setDesiredTranslation(float desiredTranslation, boolean animated) { - if (mDesiredTranslation != desiredTranslation) { - mDesiredTranslation = desiredTranslation; - if (!animated) { - setCurrentTranslation(desiredTranslation); - } else { - mTranslationAnimation.animateToFinalPosition(desiredTranslation); - } - } - } - - private void setCurrentTranslation(float currentTranslation) { - mCurrentTranslation = currentTranslation; - invalidate(); - } - - private void setTriggerBack(boolean triggerBack, boolean animated) { - if (mTriggerBack != triggerBack) { - mTriggerBack = triggerBack; - mAngleAnimation.cancel(); - updateAngle(animated); - // Whenever the trigger back state changes the existing translation animation should be - // cancelled - mTranslationAnimation.cancel(); - mBackCallback.setTriggerBack(mTriggerBack); - } - } - - private void updateAngle(boolean animated) { - float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90; - if (newAngle != mDesiredAngle) { - if (!animated) { - setCurrentAngle(newAngle); - } else { - mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce); - mAngleAnimation.animateToFinalPosition(newAngle); - } - mDesiredAngle = newAngle; - } - } - - private void setCurrentAngle(float currentAngle) { - mCurrentAngle = currentAngle; - invalidate(); - } - - private void scheduleFailsafe() { - if (!ENABLE_FAILSAFE) { - return; - } - cancelFailsafe(); - mHandler.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY_MS); - } - - private void cancelFailsafe() { - mHandler.removeCallbacks(mFailsafeRunnable); - } - - private void onFailsafe() { - setVisibility(GONE); - } - - private float dp(float dp) { - return mDensity * dp; - } - - @Override - public void dump(PrintWriter pw) { - pw.println("NavigationBarEdgePanel:"); - pw.println(" mIsLeftPanel=" + mIsLeftPanel); - pw.println(" mTriggerBack=" + mTriggerBack); - pw.println(" mDragSlopPassed=" + mDragSlopPassed); - pw.println(" mCurrentAngle=" + mCurrentAngle); - pw.println(" mDesiredAngle=" + mDesiredAngle); - pw.println(" mCurrentTranslation=" + mCurrentTranslation); - pw.println(" mDesiredTranslation=" + mDesiredTranslation); - pw.println(" mTranslationAnimation running=" + mTranslationAnimation.isRunning()); - mRegionSamplingHelper.dump(pw); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java index 10a88c8b4839..b46f2d2f5d62 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java @@ -24,16 +24,12 @@ import android.view.MotionEvent; public final class Utilities { - public static boolean isTrackpadScroll(boolean isTrackpadGestureFeaturesEnabled, - MotionEvent event) { - return isTrackpadGestureFeaturesEnabled - && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE; + public static boolean isTrackpadScroll(MotionEvent event) { + return event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE; } - public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled, - MotionEvent event) { - return isTrackpadGestureFeaturesEnabled - && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE + public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) { + return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3; } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 9698548dd30d..54a59f30c8fd 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -38,7 +38,7 @@ import java.util.concurrent.Executor import javax.inject.Inject /** Class responsible to "glue" all note task dependencies. */ -internal class NoteTaskInitializer +class NoteTaskInitializer @Inject constructor( private val controller: NoteTaskController, @@ -138,11 +138,12 @@ constructor( * Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise. */ private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? { - val entryPoint = when { - keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON - keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT - else -> null - } + val entryPoint = + when { + keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON + keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT + else -> null + } debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" } return entryPoint } @@ -164,7 +165,9 @@ constructor( // For now, trigger action immediately on UP of a single press, without waiting for // the multi-press timeout to expire. - debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" } + debugLog { + "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" + } return !isMultiPress && !isLongPress } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index ec7707c83980..e56a4f45d7aa 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.launch interface TakeScreenshotExecutor { suspend fun executeScreenshots( screenshotRequest: ScreenshotRequest, - onSaved: (Uri) -> Unit, + onSaved: (Uri?) -> Unit, requestCallback: RequestCallback ) fun onCloseSystemDialogsReceived() @@ -30,7 +30,7 @@ interface TakeScreenshotExecutor { fun onDestroy() fun executeScreenshotsAsync( screenshotRequest: ScreenshotRequest, - onSaved: Consumer<Uri>, + onSaved: Consumer<Uri?>, requestCallback: RequestCallback ) } @@ -65,7 +65,7 @@ constructor( */ override suspend fun executeScreenshots( screenshotRequest: ScreenshotRequest, - onSaved: (Uri) -> Unit, + onSaved: (Uri?) -> Unit, requestCallback: RequestCallback ) { val displayIds = getDisplaysToScreenshot(screenshotRequest.type) @@ -86,7 +86,7 @@ constructor( /** All logging should be triggered only by this method. */ private suspend fun dispatchToController( rawScreenshotData: ScreenshotData, - onSaved: (Uri) -> Unit, + onSaved: (Uri?) -> Unit, callback: RequestCallback ) { // Let's wait before logging "screenshot requested", as we should log the processed @@ -185,7 +185,7 @@ constructor( /** For java compatibility only. see [executeScreenshots] */ override fun executeScreenshotsAsync( screenshotRequest: ScreenshotRequest, - onSaved: Consumer<Uri>, + onSaved: Consumer<Uri?>, requestCallback: RequestCallback ) { mainScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt index d62ab8574799..1945c2575655 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt @@ -39,11 +39,11 @@ constructor( override suspend fun check(content: DisplayContentModel): PolicyResult { // The systemUI notification shade isn't a private profile app, skip. if (content.systemUiState.shadeExpanded) { - return NotMatched(policy = NAME, reason = "Notification shade is expanded") + return NotMatched(policy = NAME, reason = SHADE_EXPANDED) } // Find the first visible rootTaskInfo with a child task owned by a private user - val (rootTask, childTask) = + val childTask = content.rootTasks .filter { it.isVisible } .firstNotNullOfOrNull { root -> @@ -52,22 +52,24 @@ constructor( .firstOrNull { profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE } - ?.let { root to it } } - ?: return NotMatched(policy = NAME, reason = "No private profile tasks are visible") + ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS) // If matched, return parameters needed to modify the request. return Matched( policy = NAME, - reason = "At least one private profile task is visible", + reason = PRIVATE_TASK_VISIBLE, CaptureParameters( type = FullScreen(content.displayId), - component = childTask.componentName ?: rootTask.topActivity, + component = content.rootTasks.first { it.isVisible }.topActivity, owner = UserHandle.of(childTask.userId), ) ) } companion object { const val NAME = "PrivateProfile" + const val SHADE_EXPANDED = "Notification shade is expanded" + const val NO_VISIBLE_TASKS = "No private profile tasks are visible" + const val PRIVATE_TASK_VISIBLE = "At least one private profile task is visible" } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt index 3789371d7c33..f768cfb2ceb5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt @@ -30,3 +30,5 @@ internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> { ) } } + +internal fun RootTaskInfo.hasChildTasks() = childTaskUserIds.isNotEmpty() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt index b781ae99a4de..fdf16aa9d081 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt @@ -16,6 +16,7 @@ package com.android.systemui.screenshot.policy +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_PINNED import android.os.UserHandle import com.android.systemui.screenshot.data.model.DisplayContentModel @@ -24,6 +25,7 @@ import com.android.systemui.screenshot.data.repository.ProfileTypeRepository import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask +import com.android.window.flags.Flags import javax.inject.Inject import kotlinx.coroutines.flow.first @@ -41,26 +43,36 @@ constructor( override suspend fun check(content: DisplayContentModel): PolicyResult { // The systemUI notification shade isn't a work app, skip. if (content.systemUiState.shadeExpanded) { - return NotMatched(policy = NAME, reason = "Notification shade is expanded") + return NotMatched(policy = NAME, reason = SHADE_EXPANDED) + } + + if (Flags.enableDesktopWindowingMode()) { + content.rootTasks.firstOrNull()?.also { + if (it.windowingMode == WINDOWING_MODE_FREEFORM) { + return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED) + } + } } // Find the first non PiP rootTask with a top child task owned by a work user val (rootTask, childTask) = content.rootTasks - .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED } + .filter { + it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED && it.hasChildTasks() + } .map { it to it.childTasksTopDown().first() } .firstOrNull { (_, child) -> profileTypes.getProfileType(child.userId) == ProfileType.WORK } ?: return NotMatched( policy = NAME, - reason = "The top-most non-PINNED task does not belong to a work profile user" + reason = WORK_TASK_NOT_TOP, ) // If matched, return parameters needed to modify the request. return PolicyResult.Matched( policy = NAME, - reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user", + reason = WORK_TASK_IS_TOP, CaptureParameters( type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds), component = childTask.componentName ?: rootTask.topActivity, @@ -70,6 +82,13 @@ constructor( } companion object { - val NAME = "WorkProfile" + const val NAME = "WorkProfile" + const val SHADE_EXPANDED = "Notification shade is expanded" + const val WORK_TASK_NOT_TOP = + "The top-most non-PINNED task does not belong to a work profile user" + const val WORK_TASK_IS_TOP = "The top-most non-PINNED task belongs to a work profile user" + const val DESKTOP_MODE_ENABLED = + "enable_desktop_windowing_mode is enabled and top " + + "RootTask has WINDOWING_MODE_FREEFORM" } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index f418e7e0278f..8f6b9d0d19e9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -36,6 +36,7 @@ import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -64,6 +65,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, + private val communalColors: CommunalColors, @Communal private val dataSourceDelegator: SceneDataSourceDelegator, ) { /** The container view for the hub. This will not be initialized until [initView] is called. */ @@ -168,6 +170,7 @@ constructor( PlatformTheme { CommunalContainer( viewModel = communalViewModel, + colors = communalColors, dataSourceDelegator = dataSourceDelegator, dialogFactory = dialogFactory, ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index b8512f2de0a6..aa915e3721ae 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -191,6 +191,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor; import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -291,7 +292,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ public final boolean mAnimateBack; - private final boolean mTrackpadGestureFeaturesEnabled; /** * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture */ @@ -438,6 +438,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private boolean mExpandingFromHeadsUp; private boolean mCollapsedOnDown; private boolean mClosingWithAlphaFadeOut; + private boolean mHeadsUpVisible; private boolean mHeadsUpAnimatingAway; private final FalsingManager mFalsingManager; private final FalsingCollector mFalsingCollector; @@ -605,6 +606,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; private final ActiveNotificationsInteractor mActiveNotificationsInteractor; + private final HeadsUpNotificationInteractor mHeadsUpNotificationInteractor; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; @@ -770,6 +772,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ActivityStarter activityStarter, SharedNotificationContainerInteractor sharedNotificationContainerInteractor, ActiveNotificationsInteractor activeNotificationsInteractor, + HeadsUpNotificationInteractor headsUpNotificationInteractor, ShadeAnimationInteractor shadeAnimationInteractor, KeyguardViewConfigurator keyguardViewConfigurator, DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor, @@ -804,6 +807,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardTransitionInteractor = keyguardTransitionInteractor; mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor; mActiveNotificationsInteractor = activeNotificationsInteractor; + mHeadsUpNotificationInteractor = headsUpNotificationInteractor; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; @@ -886,7 +890,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; mAnimateBack = predictiveBackAnimateShade(); - mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES); mFalsingCollector = falsingCollector; mWakeUpCoordinator = coordinator; mMainDispatcher = mainDispatcher; @@ -1216,6 +1219,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } }, mMainDispatcher); + + if (NotificationsHeadsUpRefactor.isEnabled()) { + collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(), + setHeadsUpVisible(), mMainDispatcher); + } } @VisibleForTesting @@ -3055,7 +3063,21 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mPanelAlphaEndAction = r; } + private Consumer<Boolean> setHeadsUpVisible() { + return (Boolean isHeadsUpVisible) -> { + mHeadsUpVisible = isHeadsUpVisible; + + if (isHeadsUpVisible) { + updateNotificationTranslucency(); + } + updateExpansionAndVisibility(); + updateGestureExclusionRect(); + mKeyguardStatusBarViewController.updateForHeadsUp(); + }; + } + private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { + NotificationsHeadsUpRefactor.assertInLegacyMode(); mHeadsUpAnimatingAway = headsUpAnimatingAway; mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway); updateVisibility(); @@ -3071,13 +3093,16 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean shouldPanelBeVisible() { - boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode; + boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible + : (mHeadsUpAnimatingAway || mHeadsUpPinnedMode); return headsUpVisible || isExpanded() || mBouncerShowing; } private void setHeadsUpManager(HeadsUpManager headsUpManager) { mHeadsUpManager = headsUpManager; - mHeadsUpManager.addListener(mOnHeadsUpChangedListener); + if (!NotificationsHeadsUpRefactor.isEnabled()) { + mHeadsUpManager.addListener(mOnHeadsUpChangedListener); + } mHeadsUpTouchHelper = new HeadsUpTouchHelper( headsUpManager, mStatusBarService, @@ -3165,8 +3190,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean isPanelVisibleBecauseOfHeadsUp() { - return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway) - && mBarState == StatusBarState.SHADE; + boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible + : (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway); + return headsUpVisible && mBarState == StatusBarState.SHADE; } private boolean isPanelVisibleBecauseScrimIsAnimatingOff() { @@ -3479,6 +3505,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp); ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown); ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut); + ipw.print("mHeadsUpVisible="); ipw.println(mHeadsUpVisible); ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway); ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded); ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding); @@ -4384,6 +4411,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener { @Override public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) { + NotificationsHeadsUpRefactor.assertInLegacyMode(); + if (inPinnedMode) { mHeadsUpExistenceChangedRunnable.run(); updateNotificationTranslucency(); @@ -4400,9 +4429,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onHeadsUpPinned(NotificationEntry entry) { - if (NotificationsHeadsUpRefactor.isEnabled()) { - return; - } + NotificationsHeadsUpRefactor.assertInLegacyMode(); if (!isKeyguardShowing()) { mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true); @@ -4411,9 +4438,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onHeadsUpUnPinned(NotificationEntry entry) { - if (NotificationsHeadsUpRefactor.isEnabled()) { - return; - } + NotificationsHeadsUpRefactor.assertInLegacyMode(); // When we're unpinning the notification via active edge they remain heads-upped, // we need to make sure that an animation happens in this case, otherwise the @@ -4898,9 +4923,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); boolean canCollapsePanel = canCollapsePanelOnTouch(); - final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll( - mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe( - mTrackpadGestureFeaturesEnabled, event); + final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event) + || isTrackpadThreeFingerSwipe(event); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: @@ -4920,7 +4944,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mIsTrackpadReverseScroll = !mNaturalScrollingSettingObserver.isNaturalScrollingEnabled() - && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event); + && isTrackpadScroll(event); if (!isTracking() || isFullyCollapsed()) { mInitialExpandY = y; mInitialExpandX = x; @@ -5143,9 +5167,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mIgnoreXTouchSlop = true; } - final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll( - mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe( - mTrackpadGestureFeaturesEnabled, event); + final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event) + || isTrackpadThreeFingerSwipe(event); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index a763641841d9..907cf5eb6886 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -17,7 +17,6 @@ package com.android.systemui.shade; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; -import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON; import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING; import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; @@ -104,7 +103,6 @@ public class NotificationShadeWindowViewController implements Dumpable { private final PulsingGestureListener mPulsingGestureListener; private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener; private final NotificationInsetsController mNotificationInsetsController; - private final boolean mIsTrackpadCommonEnabled; private final FeatureFlagsClassic mFeatureFlagsClassic; private final SysUIKeyEventHandler mSysUIKeyEventHandler; private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; @@ -211,7 +209,6 @@ public class NotificationShadeWindowViewController implements Dumpable { mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener; mNotificationInsetsController = notificationInsetsController; mGlanceableHubContainerController = glanceableHubContainerController; - mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON); mFeatureFlagsClassic = featureFlagsClassic; mSysUIKeyEventHandler = sysUIKeyEventHandler; mPrimaryBouncerInteractor = primaryBouncerInteractor; @@ -643,16 +640,10 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mTouchActive) { final long now = mClock.uptimeMillis(); final MotionEvent event; - if (mIsTrackpadCommonEnabled) { - event = MotionEvent.obtain(mDownEvent); - event.setDownTime(now); - event.setAction(MotionEvent.ACTION_CANCEL); - event.setLocation(0.0f, 0.0f); - } else { - event = MotionEvent.obtain(now, now, - MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); - event.setSource(InputDevice.SOURCE_TOUCHSCREEN); - } + event = MotionEvent.obtain(mDownEvent); + event.setDownTime(now); + event.setAction(MotionEvent.ACTION_CANCEL); + event.setLocation(0.0f, 0.0f); Log.w(TAG, "Canceling current touch event (should be very rare)"); mView.dispatchTouchEvent(event); event.recycle(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index e3db62634339..222b070d151d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -33,9 +33,9 @@ import com.android.systemui.plugins.qs.QS import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.res.R -import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView @@ -813,7 +813,7 @@ class DragDownHelper( initialTouchX = x isTrackpadReverseScroll = !naturalScrollingSettingObserver.isNaturalScrollingEnabled && - isTrackpadScroll(true, event) + isTrackpadScroll(event) } MotionEvent.ACTION_MOVE -> { val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt index 77660eb7d864..e9306a5bd209 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.data.repository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** * A repository of currently displayed heads up notifications. @@ -31,11 +32,13 @@ interface HeadsUpRepository { * True if we are exiting the headsUp pinned mode, and some notifications might still be * animating out. This is used to keep their view container visible. */ - val isHeadsUpAnimatingAway: Flow<Boolean> + val isHeadsUpAnimatingAway: StateFlow<Boolean> /** The heads up row that should be displayed on top. */ val topHeadsUpRow: Flow<HeadsUpRowRepository?> /** Set of currently active top-level heads up rows to be displayed. */ val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>> + + fun setHeadsUpAnimatingAway(animatingAway: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt index 7f94da3c8c6a..98b52edcf9cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt @@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepository) { +class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) { val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow @@ -67,6 +67,9 @@ class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepos fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor = HeadsUpRowInteractor(key as HeadsUpRowRepository) fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey + fun setHeadsUpAnimatingAway(animatingAway: Boolean) { + repository.setHeadsUpAnimatingAway(animatingAway) + } } class HeadsUpRowInteractor(repository: HeadsUpRowRepository) 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 82559dec9f86..8a1a4f1e4cd8 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 @@ -111,6 +111,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds; @@ -450,7 +451,9 @@ public class NotificationStackScrollLayout private boolean mIsClipped; private Rect mRequestedClipBounds; private boolean mInHeadsUpPinnedMode; - private boolean mHeadsUpAnimatingAway; + @VisibleForTesting + boolean mHeadsUpAnimatingAway; + private Consumer<Boolean> mHeadsUpAnimatingAwayListener; private int mStatusBarState; private int mUpcomingStatusBarState; private boolean mHeadsUpGoingAwayAnimationsAllowed = true; @@ -4084,7 +4087,14 @@ public class NotificationStackScrollLayout mSwipeHelper.setIsExpanded(isExpanded); if (changed) { mWillExpand = false; - if (!mIsExpanded) { + if (mIsExpanded) { + // Resetting headsUpAnimatingAway on Shade expansion avoids delays caused by + // waiting for all child animations to finish. + // TODO(b/328390331) Do we need to reset this on QS expanded as well? + if (NotificationsHeadsUpRefactor.isEnabled()) { + setHeadsUpAnimatingAway(false); + } + } else { mGroupExpansionManager.collapseGroups(); mExpandHelper.cancelImmediately(); if (!mIsExpansionChanging) { @@ -4190,6 +4200,9 @@ public class NotificationStackScrollLayout void onChildAnimationFinished() { setAnimationRunning(false); + if (NotificationsHeadsUpRefactor.isEnabled()) { + setHeadsUpAnimatingAway(false); + } requestChildrenUpdate(); runAnimationFinishedRunnables(); clearTransient(); @@ -4509,18 +4522,18 @@ public class NotificationStackScrollLayout mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); if (areNotificationsHiddenInShade) { - updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0); + updateEmptyShadeViewResources(R.string.dnd_suppressing_shade_text, 0, 0); } else if (hasFilteredOutSeenNotifications) { - updateEmptyShadeView( + updateEmptyShadeViewResources( R.string.no_unseen_notif_text, R.string.unlock_to_see_notif_text, R.drawable.ic_friction_lock_closed); } else { - updateEmptyShadeView(R.string.empty_shade_text, 0, 0); + updateEmptyShadeViewResources(R.string.empty_shade_text, 0, 0); } } - private void updateEmptyShadeView( + private void updateEmptyShadeViewResources( @StringRes int newTextRes, @StringRes int newFooterTextRes, @DrawableRes int newFooterIconRes) { @@ -4717,6 +4730,7 @@ public class NotificationStackScrollLayout } public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { + NotificationsHeadsUpRefactor.assertInLegacyMode(); ExpandableNotificationRow row = entry.getHeadsUpAnimationView(); generateHeadsUpAnimation(row, isHeadsUp); } @@ -4750,6 +4764,9 @@ public class NotificationStackScrollLayout mNeedsAnimation = true; if (!mIsExpanded && !mWillExpand && !isHeadsUp) { row.setHeadsUpAnimatingAway(true); + if (NotificationsHeadsUpRefactor.isEnabled()) { + setHeadsUpAnimatingAway(true); + } } requestChildrenUpdate(); } @@ -4939,11 +4956,28 @@ public class NotificationStackScrollLayout updateClipping(); } + /** TODO(b/328390331) make this private, when {@link NotificationsHeadsUpRefactor} is removed */ public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { - mHeadsUpAnimatingAway = headsUpAnimatingAway; + if (mHeadsUpAnimatingAway != headsUpAnimatingAway) { + mHeadsUpAnimatingAway = headsUpAnimatingAway; + if (mHeadsUpAnimatingAwayListener != null) { + mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway); + } + } updateClipping(); } + /** + * Sets a listener to be notified about the heads up disappear animation state changes. If there + * are overlapping animations, it will receive updates when the first disappar animation has + * started, and when the last has finished. + * + * @param headsUpAnimatingAwayListener to be notified about disappear animation state changes. + */ + public void setHeadsUpAnimatingAwayListener( + Consumer<Boolean> headsUpAnimatingAwayListener) { + mHeadsUpAnimatingAwayListener = headsUpAnimatingAwayListener; + } @VisibleForTesting public void setStatusBarState(int statusBarState) { mStatusBarState = statusBarState; @@ -5338,7 +5372,8 @@ public class NotificationStackScrollLayout mActivityStarter.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP); }); setEmptyShadeView(view); - updateEmptyShadeView( + view.setVisible(oldView != null && oldView.isVisible(), /* animate = */ false); + updateEmptyShadeViewResources( oldView == null ? R.string.empty_shade_text : oldView.getTextResource(), oldView == null ? 0 : oldView.getFooterTextResource(), oldView == null ? 0 : oldView.getFooterIconResource()); 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 06479e5a8e0e..ea72c9b449fe 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 @@ -1482,6 +1482,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { + NotificationsHeadsUpRefactor.assertInLegacyMode(); mView.setHeadsUpAnimatingAway(headsUpAnimatingAway); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 5ab58576a89d..3a89630ebe77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor @@ -31,6 +32,7 @@ import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor +import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue @@ -64,7 +66,8 @@ constructor( userSetupInteractor: UserSetupInteractor, zenModeInteractor: ZenModeInteractor, @Background bgDispatcher: CoroutineDispatcher, -) { + dumpManager: DumpManager, +) : FlowDumperImpl(dumpManager) { /** * We want the NSSL to be unimportant for accessibility when there are no notifications in it * while the device is on lock screen, to avoid an unlabelled NSSL view in TalkBack. Otherwise, @@ -81,8 +84,9 @@ constructor( ) { hasNotifications, isShowingOnLockscreen -> hasNotifications || !isShowingOnLockscreen } - .flowOn(bgDispatcher) .distinctUntilChanged() + .dumpWhileCollecting("isImportantForAccessibility") + .flowOn(bgDispatcher) } } @@ -105,8 +109,9 @@ constructor( else -> true } } - .flowOn(bgDispatcher) .distinctUntilChanged() + .dumpWhileCollecting("shouldShowEmptyShadeView") + .flowOn(bgDispatcher) } } @@ -125,8 +130,9 @@ constructor( // the footer to be counted as part of the shade for measurements. shadeInteractor.shadeExpansion .map { it == 0f } - .flowOn(bgDispatcher) .distinctUntilChanged() + .dumpWhileCollecting("shouldHideFooterView") + .flowOn(bgDispatcher) } } @@ -173,7 +179,6 @@ constructor( else -> VisibilityChange.APPEAR_WITH_ANIMATION } } - .flowOn(bgDispatcher) .distinctUntilChanged( // Equivalent unless visibility changes areEquivalent = { a: VisibilityChange, b: VisibilityChange -> @@ -199,6 +204,8 @@ constructor( AnimatableEvent(visibilityChange.visible, shouldAnimate) } .toAnimatedValueFlow() + .dumpWhileCollecting("shouldIncludeFooterView") + .flowOn(bgDispatcher) } } @@ -213,7 +220,9 @@ constructor( if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { - zenModeInteractor.areNotificationsHiddenInShade + zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting( + "areNotificationsHiddenInShade" + ) } } @@ -222,7 +231,9 @@ constructor( if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { - seenNotificationsInteractor.hasFilteredOutSeenNotifications + seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpWhileCollecting( + "hasFilteredOutSeenNotifications" + ) } } @@ -230,7 +241,9 @@ constructor( if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { - activeNotificationsInteractor.hasClearableAlertingNotifications + activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting( + "hasClearableAlertingNotifications" + ) } } @@ -238,7 +251,9 @@ constructor( if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { - activeNotificationsInteractor.hasNonClearableSilentNotifications + activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting( + "hasNonClearableSilentNotifications" + ) } } @@ -246,7 +261,7 @@ constructor( if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) { flowOf(null) } else { - headsUpNotificationInteractor.topHeadsUpRow + headsUpNotificationInteractor.topHeadsUpRow.dumpWhileCollecting("topHeadsUpRow") } } @@ -254,15 +269,20 @@ constructor( if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) { flowOf(emptySet()) } else { - headsUpNotificationInteractor.pinnedHeadsUpRows + headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows") } } val headsUpAnimationsEnabled: Flow<Boolean> by lazy { - combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) { - (isKeyguardShowing, isShadeFullyExpanded) -> - // TODO(b/325936094) use isShadeFullyCollapsed instead - !isKeyguardShowing && !isShadeFullyExpanded + if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) { + (isKeyguardShowing, isShadeFullyExpanded) -> + // TODO(b/325936094) use isShadeFullyCollapsed instead + !isKeyguardShowing && !isShadeFullyExpanded + } + .dumpWhileCollecting("headsUpAnimationsEnabled") } } @@ -270,7 +290,7 @@ constructor( if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { - headsUpNotificationInteractor.hasPinnedRows + headsUpNotificationInteractor.hasPinnedRows.dumpWhileCollecting("hasPinnedHeadsUpRow") } } @@ -279,4 +299,8 @@ constructor( HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key)) fun elementKeyFor(key: HeadsUpRowKey): Any = headsUpNotificationInteractor.elementKeyFor(key) + + fun setHeadsUpAnimatingAway(animatingAway: Boolean) { + headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt index cb360fed77bc..6acb12a3e2af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt @@ -17,18 +17,18 @@ package com.android.systemui.statusbar.notification.ui.viewbinder import android.util.Log +import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch -private const val TAG = "HunBinder" -private val DEBUG = true // Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG) - class HeadsUpNotificationViewBinder @Inject constructor(private val viewModel: NotificationListViewModel) { @@ -39,10 +39,6 @@ constructor(private val viewModel: NotificationListViewModel) { viewModel.pinnedHeadsUpRows .sample(viewModel.headsUpAnimationsEnabled, ::Pair) .collect { (newKeys, animationsEnabled) -> - if (DEBUG) { - Log.d(TAG, "update:$newKeys") - } - val added = newKeys - previousKeys val removed = previousKeys - newKeys previousKeys = newKeys @@ -70,9 +66,19 @@ constructor(private val viewModel: NotificationListViewModel) { launch { viewModel.hasPinnedHeadsUpRow.collect { parentView.setInHeadsUpPinnedMode(it) } } + launch { + parentView.isHeadsUpAnimatingAway.collect { viewModel.setHeadsUpAnimatingAway(it) } + } } private fun obtainView(key: HeadsUpRowKey): ExpandableNotificationRow { return viewModel.elementKeyFor(key) as ExpandableNotificationRow } } + +private val NotificationStackScrollLayout.isHeadsUpAnimatingAway: Flow<Boolean> + get() = + ConflatedCallbackFlow.conflatedCallbackFlow { + setHeadsUpAnimatingAwayListener { animatingAway -> trySend(animatingAway) } + awaitClose { setHeadsUpAnimatingAwayListener(null) } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 9268d1658b80..6546db9a2868 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -24,7 +24,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter.OnDismissAction -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy @@ -37,16 +36,10 @@ class ActivityStarterImpl constructor( private val statusBarStateController: SysuiStatusBarStateController, @Main private val mainExecutor: DelayableExecutor, - legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>, - activityStarterInternal: Lazy<ActivityStarterInternalImpl>, + legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl> ) : ActivityStarter { - private val activityStarterInternal: ActivityStarterInternal = - if (SceneContainerFlag.isEnabled) { - activityStarterInternal.get() - } else { - legacyActivityStarter.get() - } + private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get() override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) { activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 0ddf37db6078..8ec8d1c814bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -168,7 +168,10 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements updateResources(); } }); - javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); + if (!NotificationsHeadsUpRefactor.isEnabled()) { + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), + this::onShadeOrQsExpanded); + } } public void setAnimationStateHandler(AnimationStateHandler handler) { @@ -262,6 +265,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements } private void onShadeOrQsExpanded(Boolean isExpanded) { + NotificationsHeadsUpRefactor.assertInLegacyMode(); if (isExpanded != mIsExpanded) { mIsExpanded = isExpanded; if (isExpanded) { @@ -500,7 +504,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements @Override @NonNull - public Flow<Boolean> isHeadsUpAnimatingAway() { + public StateFlow<Boolean> isHeadsUpAnimatingAway() { return mHeadsUpAnimatingAway; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 38b37183b97f..33437792c7ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -658,6 +658,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat updateForHeadsUp(true); } + // TODO(b/328579846) bind the StatusBar visibility to heads up events void updateForHeadsUp(boolean animate) { boolean showingKeyguardHeadsUp = isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java index 87139ac0cada..da5877b6417f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java @@ -24,6 +24,7 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -98,15 +99,21 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, // we need to keep the panel open artificially, let's wait until the //animation // is finished. - mHeadsUpManager.setHeadsUpAnimatingAway(true); + setHeadsAnimatingAway(true); mNsslController.runAfterAnimationFinished(() -> { if (!mHeadsUpManager.hasPinnedHeadsUp()) { mNotificationShadeWindowController.setHeadsUpShowing(false); - mHeadsUpManager.setHeadsUpAnimatingAway(false); + setHeadsAnimatingAway(false); } mNotificationRemoteInputManager.onPanelCollapsed(); }); } } } + + private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) { + if (!NotificationsHeadsUpRefactor.isEnabled()) { + mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index e48b6397457c..263ddc175647 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -43,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.CoreStartable; +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.qualifiers.Main; @@ -55,6 +56,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.onehanded.OneHanded; @@ -124,6 +126,8 @@ public final class WMShell implements private final UserTracker mUserTracker; private final DisplayTracker mDisplayTracker; private final NoteTaskInitializer mNoteTaskInitializer; + private final CommunalTransitionViewModel mCommunalTransitionViewModel; + private final JavaAdapter mJavaAdapter; private final Executor mSysUiMainExecutor; // Listeners and callbacks. Note that we prefer member variable over anonymous class here to @@ -187,6 +191,8 @@ public final class WMShell implements UserTracker userTracker, DisplayTracker displayTracker, NoteTaskInitializer noteTaskInitializer, + CommunalTransitionViewModel communalTransitionViewModel, + JavaAdapter javaAdapter, @Main Executor sysUiMainExecutor) { mContext = context; mShell = shell; @@ -205,6 +211,8 @@ public final class WMShell implements mUserTracker = userTracker; mDisplayTracker = displayTracker; mNoteTaskInitializer = noteTaskInitializer; + mCommunalTransitionViewModel = communalTransitionViewModel; + mJavaAdapter = javaAdapter; mSysUiMainExecutor = sysUiMainExecutor; } @@ -381,6 +389,8 @@ public final class WMShell implements void initRecentTasks(RecentTasks recentTasks) { recentTasks.addAnimationStateListener(mSysUiMainExecutor, mCommandQueue::onRecentsAnimationStateChanged); + mJavaAdapter.alwaysCollectFlow(mCommunalTransitionViewModel.getRecentsBackgroundColor(), + recentTasks::setTransitionBackgroundColor); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java index 7c121e1754bf..d7bd59e43e30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java @@ -31,36 +31,57 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; -import android.testing.AndroidTestingRunner; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import androidx.test.filters.SmallTest; import com.android.settingslib.SliceBroadcastRelay; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +import java.util.List; + +@RunWith(Parameterized.class) @SmallTest public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { + @Parameterized.Parameters(name = "{0}") + public static List<FlagsParameterization> getFlags() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND); + } + private static final String TEST_ACTION = "com.android.systemui.action.TEST_ACTION"; + private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); + private SliceBroadcastRelayHandler mRelayHandler; private Context mSpyContext; @Mock private BroadcastDispatcher mBroadcastDispatcher; + + public SliceBroadcastRelayHandlerTest(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setup() { MockitoAnnotations.initMocks(this); mSpyContext = spy(mContext); - mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext, mBroadcastDispatcher); + mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext, mBroadcastDispatcher, + mBackgroundExecutor); } @Test @@ -80,6 +101,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri); mRelayHandler.handleIntent(intent); + mBackgroundExecutor.runAllReady(); verify(mSpyContext).registerReceiver(any(), eq(value), anyInt()); } @@ -99,12 +121,14 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value); mRelayHandler.handleIntent(intent); + mBackgroundExecutor.runAllReady(); ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt()); intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER); intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); mRelayHandler.handleIntent(intent); + mBackgroundExecutor.runAllReady(); verify(mSpyContext).unregisterReceiver(eq(relay.getValue())); } @@ -119,6 +143,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { Intent intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER); intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); mRelayHandler.handleIntent(intent); + mBackgroundExecutor.runAllReady(); // No crash } @@ -138,6 +163,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value); mRelayHandler.handleIntent(intent); + mBackgroundExecutor.runAllReady(); ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt()); relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION)); @@ -146,8 +172,10 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { } @Test - public void testRegisteredWithDispatcher() { + @DisableFlags(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND) + public void testRegisteredWithDispatcher_onMainThread() { mRelayHandler.start(); + mBackgroundExecutor.runAllReady(); verify(mBroadcastDispatcher) .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class)); @@ -155,6 +183,19 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class)); } + @Test + @EnableFlags(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND) + public void testRegisteredWithDispatcher_onBackgroundThread() { + mRelayHandler.start(); + mBackgroundExecutor.runAllReady(); + + verify(mBroadcastDispatcher) + .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class), + eq(mBackgroundExecutor)); + verify(mSpyContext, never()) + .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class)); + } + public static class Receiver extends BroadcastReceiver { private static BroadcastReceiver sReceiver; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index c900463c7159..0f3714385725 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.lang.IllegalStateException +import java.util.function.Consumer import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent @@ -78,7 +79,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_severalDisplays_callsControllerForEachOne() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) verify(controllerFactory).create(eq(0), any()) @@ -107,7 +108,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots( createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE), onSaved, @@ -136,7 +137,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() = testScope.runTest { setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) verifyNoMoreInteractions(controllerFactory) @@ -154,7 +155,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { display(TYPE_OVERLAY, id = 2), display(TYPE_WIFI, id = 3) ) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) verify(controller0, times(4)).handleScreenshot(any(), any(), any()) @@ -165,7 +166,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() @@ -190,7 +191,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() @@ -217,7 +218,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_allDisplaysFail_reportsFail() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() @@ -244,7 +245,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun onDestroy_propagatedToControllers() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onDestroy() @@ -256,7 +257,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun removeWindows_propagatedToControllers() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.removeWindows() @@ -270,7 +271,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun onCloseSystemDialogsReceived_propagatedToControllers() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() @@ -286,7 +287,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) whenever(controller0.isPendingSharedTransition).thenReturn(true) whenever(controller1.isPendingSharedTransition).thenReturn(false) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() @@ -304,7 +305,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val toBeReturnedByProcessor = ScreenshotData.forTesting() requestProcessor.toReturn = toBeReturnedByProcessor - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(screenshotRequest, onSaved, callback) assertThat(requestProcessor.processed) @@ -321,7 +322,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } requestProcessor.shouldThrowException = true screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -338,7 +339,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_errorFromProcessor_logsUiError() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } requestProcessor.shouldThrowException = true screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -355,7 +356,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } requestProcessor.shouldThrowException = true screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -368,7 +369,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } requestProcessor.shouldThrowException = true screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -381,7 +382,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_errorFromScreenshotController_reportsRequested() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } whenever(controller0.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) whenever(controller1.handleScreenshot(any(), any(), any())) @@ -401,7 +402,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_errorFromScreenshotController_reportsError() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } whenever(controller0.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) whenever(controller1.handleScreenshot(any(), any(), any())) @@ -421,7 +422,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - val onSaved = { _: Uri -> } + val onSaved = { _: Uri? -> } whenever(controller0.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) whenever(controller1.handleScreenshot(any(), any(), any())) @@ -434,6 +435,25 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.onDestroy() } + @Test + fun executeScreenshots_finisherCalledWithNullUri_succeeds() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0)) + var onSavedCallCount = 0 + val onSaved: (Uri?) -> Unit = { + assertThat(it).isNull() + onSavedCallCount += 1 + } + whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer { + (it.getArgument(1) as Consumer<Uri?>).accept(null) + } + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + assertThat(onSavedCallCount).isEqualTo(1) + + screenshotExecutor.onDestroy() + } + private suspend fun TestScope.setDisplays(vararg displays: Display) { fakeDisplayRepository.emit(displays.toSet()) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 0776aa7d1845..77b5c9115295 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -232,7 +232,7 @@ internal class FakeScreenshotExecutor : TakeScreenshotExecutor { override fun onCloseSystemDialogsReceived() {} override suspend fun executeScreenshots( screenshotRequest: ScreenshotRequest, - onSaved: (Uri) -> Unit, + onSaved: (Uri?) -> Unit, requestCallback: RequestCallback, ) { requestReceived = screenshotRequest @@ -248,7 +248,7 @@ internal class FakeScreenshotExecutor : TakeScreenshotExecutor { override fun executeScreenshotsAsync( screenshotRequest: ScreenshotRequest, - onSaved: Consumer<Uri>, + onSaved: Consumer<Uri?>, requestCallback: RequestCallback, ) { runBlocking { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt index 621b0582c538..254f1e1efe13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt @@ -188,6 +188,18 @@ object DisplayContentScenarios { * actual values returned by ActivityTaskManager */ object RootTasks { + /** An empty RootTaskInfo with no child tasks. */ + val emptyWithNoChildTasks = + newRootTaskInfo( + taskId = 2, + visible = true, + running = true, + numActivities = 0, + bounds = FULL_SCREEN, + ) { + emptyList() + } + /** * The empty RootTaskInfo that is always at the end of a list from ActivityTaskManager when * no other visible activities are in split mode diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt new file mode 100644 index 000000000000..9e3ae054d31b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2024 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.screenshot.policy + +import android.content.ComponentName +import android.os.UserHandle +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.screenshot.data.model.DisplayContentModel +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE_PIP +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.emptyRootSplit +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.fullScreen +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.launcher +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps +import com.android.systemui.screenshot.data.model.SystemUiState +import com.android.systemui.screenshot.data.repository.profileTypeRepository +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched +import com.android.systemui.screenshot.policy.CaptureType.FullScreen +import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL +import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class PrivateProfilePolicyTest { + private val kosmos = Kosmos() + private val policy = PrivateProfilePolicy(kosmos.profileTypeRepository) + + // TODO: + // private app in PIP + // private app below personal PIP app + // Freeform windows + + @Test + fun shadeExpanded_notMatched() = runTest { + val result = + policy.check( + singleFullScreen( + spec = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE), + shadeExpanded = true + ) + ) + + assertThat(result) + .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.SHADE_EXPANDED)) + } + + @Test + fun noPrivate_notMatched() = runTest { + val result = + policy.check( + singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL)) + ) + + assertThat(result) + .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.NO_VISIBLE_TASKS)) + } + + @Test + fun withPrivateFullScreen_isMatched() = runTest { + val result = + policy.check( + singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE)) + ) + + assertThat(result) + .isEqualTo( + Matched( + PrivateProfilePolicy.NAME, + PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, + CaptureParameters( + type = FullScreen(displayId = 0), + component = ComponentName.unflattenFromString(YOUTUBE), + owner = UserHandle.of(PRIVATE) + ) + ) + ) + } + + @Test + fun withPrivateNotVisible_notMatched() = runTest { + val result = + policy.check( + DisplayContentModel( + displayId = 0, + systemUiState = SystemUiState(shadeExpanded = false), + rootTasks = + listOf( + fullScreen( + TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL), + visible = true + ), + fullScreen( + TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), + visible = false + ), + launcher(visible = false), + emptyRootSplit, + ) + ) + ) + + assertThat(result) + .isEqualTo( + NotMatched( + PrivateProfilePolicy.NAME, + PrivateProfilePolicy.NO_VISIBLE_TASKS, + ) + ) + } + + @Test + fun withPrivateFocusedInSplitScreen_isMatched() = runTest { + val result = + policy.check( + splitScreenApps( + top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL), + bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), + focusedTaskId = 1003 + ) + ) + + assertThat(result) + .isEqualTo( + Matched( + PrivateProfilePolicy.NAME, + PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, + CaptureParameters( + type = FullScreen(displayId = 0), + component = ComponentName.unflattenFromString(YOUTUBE), + owner = UserHandle.of(PRIVATE) + ) + ) + ) + } + + @Test + fun withPrivateNotFocusedInSplitScreen_isMatched() = runTest { + val result = + policy.check( + splitScreenApps( + top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL), + bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), + focusedTaskId = 1002 + ) + ) + + assertThat(result) + .isEqualTo( + Matched( + PrivateProfilePolicy.NAME, + PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, + CaptureParameters( + type = FullScreen(displayId = 0), + component = ComponentName.unflattenFromString(FILES), + owner = UserHandle.of(PRIVATE) + ) + ) + ) + } + + @Test + fun withPrivatePictureInPictureApp_isMatched() = runTest { + val result = + policy.check( + pictureInPictureApp(TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PRIVATE)) + ) + + assertThat(result) + .isEqualTo( + Matched( + PrivateProfilePolicy.NAME, + PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, + CaptureParameters( + type = FullScreen(displayId = 0), + component = ComponentName.unflattenFromString(YOUTUBE_PIP), + owner = UserHandle.of(PRIVATE) + ) + ) + ) + } + + @Test + fun withPrivateAppBelowPictureInPictureApp_isMatched() = runTest { + val result = + policy.check( + pictureInPictureApp( + pip = TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PERSONAL), + fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PRIVATE), + ) + ) + + assertThat(result) + .isEqualTo( + Matched( + PrivateProfilePolicy.NAME, + PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, + CaptureParameters( + type = FullScreen(displayId = 0), + component = ComponentName.unflattenFromString(YOUTUBE_PIP), + owner = UserHandle.of(PRIVATE) + ) + ) + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt new file mode 100644 index 000000000000..5d35528b0cf0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2024 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.screenshot.policy + +import android.content.ComponentName +import android.os.UserHandle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.screenshot.data.model.DisplayContentModel +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_TOP +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.freeFormApps +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps +import com.android.systemui.screenshot.data.model.SystemUiState +import com.android.systemui.screenshot.data.repository.profileTypeRepository +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched +import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask +import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL +import com.android.systemui.screenshot.policy.TestUserIds.WORK +import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.DESKTOP_MODE_ENABLED +import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.SHADE_EXPANDED +import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_IS_TOP +import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_NOT_TOP +import com.android.window.flags.Flags +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class WorkProfilePolicyTest { + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + private val kosmos = Kosmos() + private val policy = WorkProfilePolicy(kosmos.profileTypeRepository) + + /** + * There is no guarantee that every RootTaskInfo contains a non-empty list of child tasks. Test + * the case where the RootTaskInfo would match but child tasks are empty. + */ + @Test + fun withEmptyChildTasks_notMatched() = runTest { + val result = + policy.check( + DisplayContentModel( + displayId = 0, + systemUiState = SystemUiState(shadeExpanded = false), + rootTasks = listOf(RootTasks.emptyWithNoChildTasks) + ) + ) + + assertThat(result) + .isEqualTo( + NotMatched( + WorkProfilePolicy.NAME, + WORK_TASK_NOT_TOP, + ) + ) + } + + @Test + fun noWorkApp_notMatched() = runTest { + val result = + policy.check( + singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL)) + ) + + assertThat(result) + .isEqualTo( + NotMatched( + WorkProfilePolicy.NAME, + WORK_TASK_NOT_TOP, + ) + ) + } + + @Test + fun withWorkFullScreen_shadeExpanded_notMatched() = runTest { + val result = + policy.check( + singleFullScreen( + TaskSpec(taskId = 1002, name = FILES, userId = WORK), + shadeExpanded = true + ) + ) + + assertThat(result) + .isEqualTo( + NotMatched( + WorkProfilePolicy.NAME, + SHADE_EXPANDED, + ) + ) + } + + @Test + fun withWorkFullScreen_matched() = runTest { + val result = + policy.check(singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK))) + + assertThat(result) + .isEqualTo( + PolicyResult.Matched( + policy = WorkProfilePolicy.NAME, + reason = WORK_TASK_IS_TOP, + CaptureParameters( + type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN), + component = ComponentName.unflattenFromString(FILES), + owner = UserHandle.of(WORK), + ) + ) + ) + } + + @Test + fun withWorkFocusedInSplitScreen_matched() = runTest { + val result = + policy.check( + splitScreenApps( + top = TaskSpec(taskId = 1002, name = FILES, userId = WORK), + bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), + focusedTaskId = 1002 + ) + ) + + assertThat(result) + .isEqualTo( + PolicyResult.Matched( + policy = WorkProfilePolicy.NAME, + reason = WORK_TASK_IS_TOP, + CaptureParameters( + type = IsolatedTask(taskId = 1002, taskBounds = SPLIT_TOP), + component = ComponentName.unflattenFromString(FILES), + owner = UserHandle.of(WORK), + ) + ) + ) + } + + @Test + fun withWorkNotFocusedInSplitScreen_notMatched() = runTest { + val result = + policy.check( + splitScreenApps( + top = TaskSpec(taskId = 1002, name = FILES, userId = WORK), + bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), + focusedTaskId = 1003 + ) + ) + + assertThat(result) + .isEqualTo( + NotMatched( + WorkProfilePolicy.NAME, + WORK_TASK_NOT_TOP, + ) + ) + } + + @Test + fun withWorkBelowPersonalPictureInPicture_matched() = runTest { + val result = + policy.check( + pictureInPictureApp( + pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL), + fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK), + ) + ) + + assertThat(result) + .isEqualTo( + PolicyResult.Matched( + policy = WorkProfilePolicy.NAME, + reason = WORK_TASK_IS_TOP, + CaptureParameters( + type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN), + component = ComponentName.unflattenFromString(FILES), + owner = UserHandle.of(WORK), + ) + ) + ) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun withWorkFocusedInFreeForm_matched() = runTest { + val result = + policy.check( + freeFormApps( + TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL), + TaskSpec(taskId = 1003, name = FILES, userId = WORK), + focusedTaskId = 1003 + ) + ) + + assertThat(result) + .isEqualTo( + PolicyResult.Matched( + policy = WorkProfilePolicy.NAME, + reason = WORK_TASK_IS_TOP, + CaptureParameters( + type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM), + component = ComponentName.unflattenFromString(FILES), + owner = UserHandle.of(WORK), + ) + ) + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun withWorkFocusedInFreeForm_desktopModeEnabled_notMatched() = runTest { + val result = + policy.check( + freeFormApps( + TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL), + TaskSpec(taskId = 1003, name = FILES, userId = WORK), + focusedTaskId = 1003 + ) + ) + + assertThat(result) + .isEqualTo( + NotMatched( + WorkProfilePolicy.NAME, + DESKTOP_MODE_ENABLED, + ) + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index e611da0044e6..ee03236d00b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -85,6 +86,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var dialogFactory: SystemUIDialogFactory + @Mock private lateinit var communalColors: CommunalColors private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var shadeInteractor: ShadeInteractor private lateinit var keyguardInteractor: KeyguardInteractor @@ -116,6 +118,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { keyguardInteractor, shadeInteractor, powerManager, + communalColors, kosmos.sceneDataSourceDelegator, ) testableLooper = TestableLooper.get(this) @@ -156,6 +159,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { keyguardInteractor, shadeInteractor, powerManager, + communalColors, kosmos.sceneDataSourceDelegator, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 0a8e470f8a7c..7a39a0d01534 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -154,6 +154,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger; import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor; import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -161,6 +162,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; +import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository; import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; @@ -358,6 +360,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected TestScope mTestScope = mKosmos.getTestScope(); protected ShadeInteractor mShadeInteractor; protected PowerInteractor mPowerInteractor; + protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository = + new FakeHeadsUpNotificationRepository(); + protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor = + new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository); protected NotificationPanelViewController.TouchHandler mTouchHandler; protected ConfigurationController mConfigurationController; protected SysuiStatusBarStateController mStatusBarStateController; @@ -384,7 +390,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false); mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); @@ -730,6 +735,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mActivityStarter, mSharedNotificationContainerInteractor, mActiveNotificationsInteractor, + mHeadsUpNotificationInteractor, mShadeAnimationInteractor, mKeyguardViewConfigurator, mDeviceEntryFaceAuthInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 650c45bf83ea..81e20c17a8ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -46,6 +46,7 @@ import android.animation.Animator; import android.animation.ValueAnimator; import android.graphics.Point; import android.os.PowerManager; +import android.platform.test.annotations.DisableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -62,6 +63,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; @@ -1287,6 +1289,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) public void shadeExpanded_whenHunIsPresent() { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); assertThat(mNotificationPanelViewController.isExpanded()).isTrue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt index 4df7ef533c7a..6631d29da719 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -14,8 +14,11 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.shade +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.HapticFeedbackConstants @@ -29,10 +32,14 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED +import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor +import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle @@ -235,4 +242,41 @@ class NotificationPanelViewControllerWithCoroutinesTest : val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha) assertThat(bottomAreaAlpha).isEqualTo(1f) } + + @Test + @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + fun shadeExpanded_whenHunIsPresent() = runTest { + launch(mainDispatcher) { + givenViewAttached() + + // WHEN a pinned heads up is present + mFakeHeadsUpNotificationRepository.setNotifications( + fakeHeadsUpRowRepository("key", isPinned = true) + ) + } + advanceUntilIdle() + + // THEN the panel should be visible + assertThat(mNotificationPanelViewController.isExpanded).isTrue() + } + + @Test + @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + fun shadeExpanded_whenHunIsAnimatingAway() = runTest { + launch(mainDispatcher) { + givenViewAttached() + + // WHEN a heads up is animating away + mFakeHeadsUpNotificationRepository.isHeadsUpAnimatingAway.value = true + } + advanceUntilIdle() + + // THEN the panel should be visible + assertThat(mNotificationPanelViewController.isExpanded).isTrue() + } + + private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = + FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { + this.isPinned.value = isPinned + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 8c5a4d0bbcae..da09579e1bde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -38,8 +38,6 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION -import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON -import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -166,8 +164,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { .thenReturn(emptyFlow<TransitionStep>()) featureFlagsClassic = FakeFeatureFlagsClassic() - featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true) - featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false) featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index ba8eb6f4ba36..f380b6c700cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -155,8 +155,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { .thenReturn(emptyFlow()) val featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) - featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 1e058cac8001..89ae9f4e3547 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -90,6 +90,7 @@ import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefac import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -106,6 +107,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; +import java.util.function.Consumer; /** * Tests for {@link NotificationStackScrollLayout}. @@ -1044,6 +1046,96 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertFalse(mStackScroller.getIsBeingDragged()); } + + @Test + @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() { + // GIVEN NSSL is ready for HUN animations + Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); + + // WHEN we generate a disappear event + mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false); + + // THEN headsUpAnimatingAway is true + verify(headsUpAnimatingAwayListener).accept(true); + assertTrue(mStackScroller.mHeadsUpAnimatingAway); + } + + @Test + @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() { + // GIVEN NSSL would be ready for HUN animations, BUT it is expanded + Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + assertTrue("Should be expanded by default.", mStackScroller.isExpanded()); + mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener); + mStackScroller.setAnimationsEnabled(true); + mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true); + + // WHEN we generate a disappear event + mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false); + + // THEN nothing happens + verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean()); + assertFalse(mStackScroller.mHeadsUpAnimatingAway); + } + + @Test + @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() { + // GIVEN NSSL is ready for HUN animations + Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); + // BUT there is a pending appear event + mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true); + + // WHEN we generate a disappear event + mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false); + + // THEN nothing happens + verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean()); + assertFalse(mStackScroller.mHeadsUpAnimatingAway); + } + + @Test + @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() { + // GIVEN NSSL is ready for HUN animations + Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); + + // WHEN we generate a disappear event + mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true); + + // THEN headsUpAnimatingWay is not set + verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean()); + assertFalse(mStackScroller.mHeadsUpAnimatingAway); + } + + @Test + @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() { + // GIVEN NSSL is ready for HUN animations + Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); + + // AND there is a HUN animating away + mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false); + assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway); + + // WHEN the child animations are finished + mStackScroller.onChildAnimationFinished(); + + // THEN headsUpAnimatingAway is false + verify(headsUpAnimatingAwayListener).accept(false); + assertFalse(mStackScroller.mHeadsUpAnimatingAway); + } + private MotionEvent captureTouchSentToSceneFramework() { ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class); verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture()); @@ -1056,6 +1148,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScroller.setStatusBarState(state); } + private void prepareStackScrollerForHunAnimations( + Consumer<Boolean> headsUpAnimatingAwayListener) { + mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener); + mStackScroller.setIsExpanded(false); + mStackScroller.setAnimationsEnabled(true); + mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true); + } + private ExpandableNotificationRow createClearableRow() { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); NotificationEntry entry = mock(NotificationEntry.class); @@ -1116,4 +1216,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertThat(mActual.getY()).isEqualTo(expected.getY()); } } + + private abstract static class BooleanConsumer implements Consumer<Boolean> { } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java deleted file mode 100644 index d2c8aea5988f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2020 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.wmshell; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; - -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.model.SysUiState; -import com.android.systemui.notetask.NoteTaskInitializer; -import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.desktopmode.DesktopMode; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; -import com.android.wm.shell.onehanded.OneHanded; -import com.android.wm.shell.onehanded.OneHandedEventCallback; -import com.android.wm.shell.onehanded.OneHandedTransitionCallback; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.recents.RecentTasks; -import com.android.wm.shell.splitscreen.SplitScreen; -import com.android.wm.shell.sysui.ShellInterface; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; -import java.util.concurrent.Executor; - -/** - * Tests for {@link WMShell}. - * - * Build/Install/Run: - * atest SystemUITests:WMShellTest - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class WMShellTest extends SysuiTestCase { - WMShell mWMShell; - - @Mock ShellInterface mShellInterface; - @Mock CommandQueue mCommandQueue; - @Mock ConfigurationController mConfigurationController; - @Mock KeyguardStateController mKeyguardStateController; - @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock ScreenLifecycle mScreenLifecycle; - @Mock SysUiState mSysUiState; - @Mock Pip mPip; - @Mock SplitScreen mSplitScreen; - @Mock OneHanded mOneHanded; - @Mock WakefulnessLifecycle mWakefulnessLifecycle; - @Mock UserTracker mUserTracker; - @Mock ShellExecutor mSysUiMainExecutor; - @Mock NoteTaskInitializer mNoteTaskInitializer; - @Mock DesktopMode mDesktopMode; - @Mock RecentTasks mRecentTasks; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext); - mWMShell = new WMShell( - mContext, - mShellInterface, - Optional.of(mPip), - Optional.of(mSplitScreen), - Optional.of(mOneHanded), - Optional.of(mDesktopMode), - Optional.of(mRecentTasks), - mCommandQueue, - mConfigurationController, - mKeyguardStateController, - mKeyguardUpdateMonitor, - mScreenLifecycle, - mSysUiState, - mWakefulnessLifecycle, - mUserTracker, - displayTracker, - mNoteTaskInitializer, - mSysUiMainExecutor - ); - } - - @Test - public void initPip_registersCommandQueueCallback() { - mWMShell.initPip(mPip); - - verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class)); - } - - @Test - public void initOneHanded_registersCallbacks() { - mWMShell.initOneHanded(mOneHanded); - - verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class)); - verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class)); - verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class)); - verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class)); - } - - @Test - public void initDesktopMode_registersListener() { - mWMShell.initDesktopMode(mDesktopMode); - verify(mDesktopMode).addVisibleTasksListener( - any(DesktopModeTaskRepository.VisibleTasksListener.class), - any(Executor.class)); - } - - @Test - public void initRecentTasks_registersListener() { - mWMShell.initRecentTasks(mRecentTasks); - verify(mRecentTasks).addAnimationStateListener(any(Executor.class), any()); - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt index e36ddc17e5a8..e3c218df2c53 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.util.communalColors import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel @@ -37,5 +38,6 @@ val Kosmos.communalTransitionViewModel by glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel, communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, + communalColors = communalColors, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt new file mode 100644 index 000000000000..e76cf68af05b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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.communal.util + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.communalColors: CommunalColors by Kosmos.Fixture { fakeCommunalColors } +val Kosmos.fakeCommunalColors by Kosmos.Fixture { FakeCommunalColors() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt new file mode 100644 index 000000000000..7046658728a0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 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.communal.util + +import android.graphics.Color +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeCommunalColors : CommunalColors { + private val _backgroundColor = MutableStateFlow(Color.valueOf(Color.BLACK)) + + override val backgroundColor: StateFlow<Color> + get() = _backgroundColor.asStateFlow() + + fun setBackgroundColor(color: Color) { + _backgroundColor.value = color + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt index dc1b9feea88f..7bf77e5199fe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt @@ -30,4 +30,7 @@ class FakeHeadsUpNotificationRepository : HeadsUpRepository { override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null) override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> = MutableStateFlow(emptySet()) + override fun setHeadsUpAnimatingAway(animatingAway: Boolean) { + isHeadsUpAnimatingAway.value = animatingAway + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt index c65d0a33cf67..94f6ecd36c7c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -48,5 +49,6 @@ val Kosmos.notificationListViewModel by Fixture { userSetupInteractor, zenModeInteractor, testDispatcher, + dumpManager, ) } diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 8ab2e0fa6379..5846c924ad64 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -91,6 +91,16 @@ flag { } flag { + name: "focus_click_point_window_bounds_from_a11y_window_info" + namespace: "accessibility" + description: "Uses A11yWindowInfo bounds for focus click point bounds checking" + bug: "317166487" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "fullscreen_fling_gesture" namespace: "accessibility" description: "When true, adds a fling gesture animation for fullscreen magnification" diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index ccf9a90b5964..3378bf29e516 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5234,7 +5234,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub //Clip to the window bounds. Rect windowBounds = mTempRect1; - getWindowBounds(focus.getWindowId(), windowBounds); + if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) { + AccessibilityWindowInfo window = focus.getWindow(); + if (window != null) { + window.getBoundsInScreen(windowBounds); + } + } else { + getWindowBounds(focus.getWindowId(), windowBounds); + } if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) { return false; } diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java index b119d7d117cd..853b824218f2 100644 --- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -17,6 +17,7 @@ package com.android.server.accessibility; import android.accessibilityservice.AccessibilityService; +import android.annotation.RequiresNoPermission; import android.os.Binder; import android.os.RemoteException; import android.util.Slog; @@ -34,7 +35,6 @@ import java.util.List; * If we are stripping and/or replacing the actions from a window, we need to intercept the * nodes heading back to the service and swap out the actions. */ -@SuppressWarnings("MissingPermissionAnnotation") public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub { private static final boolean DEBUG = false; private static final String LOG_TAG = "ActionReplacingCallback"; @@ -97,6 +97,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection } @Override + @RequiresNoPermission public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) { synchronized (mLock) { if (interactionId == mInteractionId) { @@ -114,6 +115,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection } @Override + @RequiresNoPermission public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, int interactionId) { synchronized (mLock) { @@ -132,6 +134,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection } @Override + @RequiresNoPermission public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos, int interactionId) throws RemoteException { @@ -163,6 +166,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection } @Override + @RequiresNoPermission public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) throws RemoteException { // There's no reason to use this class when performing actions. Do something reasonable. @@ -170,6 +174,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection } @Override + @RequiresNoPermission public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId) throws RemoteException { mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId); @@ -285,6 +290,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection } @Override + @RequiresNoPermission public void sendAttachOverlayResult( @AccessibilityService.AttachOverlayResult int result, int interactionId) throws RemoteException { diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index c96688c1b9ae..7ceb3bb56403 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.os.Handler; +import android.os.IBinder; import android.os.ICancellationSignal; import android.os.RemoteException; import android.service.autofill.AutofillService; @@ -42,7 +43,6 @@ import android.service.autofill.ISaveCallback; import android.service.autofill.SaveRequest; import android.text.format.DateUtils; import android.util.Slog; -import android.view.autofill.IAutoFillManagerClient; import com.android.internal.infra.AbstractRemoteService; import com.android.internal.infra.ServiceConnector; @@ -283,8 +283,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { return callback; } - public void onFillCredentialRequest(@NonNull FillRequest request, - IAutoFillManagerClient autofillCallback) { + public void onFillCredentialRequest(@NonNull FillRequest request, IBinder autofillCallback) { if (sVerbose) { Slog.v(TAG, "onFillRequest:" + request); } diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java index ce9d1803d764..044a06417c00 100644 --- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java +++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java @@ -21,11 +21,11 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; +import android.os.IBinder; import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.util.Slog; -import android.view.autofill.IAutoFillManagerClient; /** * Requests autofill response from a Remote Autofill Service. This autofill service can be @@ -105,8 +105,7 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal /** * Requests a new fill response. */ - public void onFillRequest(FillRequest pendingFillRequest, int flag, - IAutoFillManagerClient client) { + public void onFillRequest(FillRequest pendingFillRequest, int flag, IBinder client) { Slog.v(TAG, "Requesting fill response to secondary provider."); mLastFlag = flag; if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 3a384065217e..cd1ef882868a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -757,13 +757,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingInlineSuggestionsRequest, id); } mSecondaryProviderHandler.onFillRequest(mPendingFillRequest, - mPendingFillRequest.getFlags(), mClient); + mPendingFillRequest.getFlags(), mClient.asBinder()); } else if (mRemoteFillService != null) { if (mIsPrimaryCredential) { mPendingFillRequest = addCredentialManagerDataToClientState( mPendingFillRequest, mPendingInlineSuggestionsRequest, id); - mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient); + mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, + mClient.asBinder()); } else { mRemoteFillService.onFillRequest(mPendingFillRequest); } @@ -2897,7 +2898,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", clientState=" + newClientState + ", authenticationId=" + authenticationId); } if (Flags.autofillCredmanDevIntegration() && exception != null - && exception instanceof GetCredentialException) { + && !exception.getType().equals(GetCredentialException.TYPE_USER_CANCELED)) { if (dataset != null && dataset.getFieldIds().size() == 1) { if (sDebug) { Slog.d(TAG, "setAuthenticationResultLocked(): result returns with" @@ -6494,21 +6495,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } if (exception != null) { - mClient.onGetCredentialException(id, viewId, exception.getType(), - exception.getMessage()); + if (viewId.isVirtualInt()) { + sendResponseToViewNode(viewId, /*response=*/ null, exception); + } else { + mClient.onGetCredentialException(id, viewId, exception.getType(), + exception.getMessage()); + } } else if (response != null) { if (viewId.isVirtualInt()) { - ViewNode viewNode = getViewNodeFromContextsLocked(viewId); - if (viewNode != null && viewNode.getPendingCredentialCallback() != null) { - Bundle resultData = new Bundle(); - resultData.putParcelable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, - response); - viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, - resultData); - } else { - Slog.w(TAG, "View node not found after GetCredentialResponse"); - } + sendResponseToViewNode(viewId, response, /*exception=*/ null); } else { mClient.onGetCredentialResponse(id, viewId, response); } @@ -6522,6 +6517,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @GuardedBy("mLock") + private void sendResponseToViewNode(AutofillId viewId, GetCredentialResponse response, + GetCredentialException exception) { + ViewNode viewNode = getViewNodeFromContextsLocked(viewId); + if (viewNode != null && viewNode.getPendingCredentialCallback() != null) { + Bundle resultData = new Bundle(); + if (response != null) { + resultData.putParcelable( + CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, + response); + viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, + resultData); + } else if (exception != null) { + resultData.putStringArray( + CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, + new String[] {exception.getType(), exception.getMessage()}); + viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR, + resultData); + } + } else { + Slog.w(TAG, "View node not found after GetCredentialResponse"); + } + } + void autoFillApp(Dataset dataset) { synchronized (mLock) { if (mDestroyed) { diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java index 82e9a26310e8..7a2106bb7753 100644 --- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java +++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java @@ -175,7 +175,7 @@ class BackupRestoreProcessor { // Create a new association reassigned to this user and a valid association ID final String packageName = restored.getPackageName(); - final int newId = mAssociationStore.getNextId(userId); + final int newId = mAssociationStore.getNextId(); AssociationInfo newAssociation = new AssociationInfo.Builder(newId, userId, packageName, restored).build(); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 9cfb5351f6cf..c892b84ab4d5 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -85,7 +85,7 @@ class CompanionDeviceShellCommand extends ShellCommand { final int userId = getNextIntArgRequired(); final List<AssociationInfo> associationsForUser = mAssociationStore.getActiveAssociationsByUser(userId); - final int maxId = mAssociationStore.getMaxId(userId); + final int maxId = mAssociationStore.getMaxId(); out.println("Max ID: " + maxId); out.println("Association ID | Package Name | Mac Address"); for (AssociationInfo association : associationsForUser) { diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index a18776e67200..1f09d4da6260 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -284,7 +284,7 @@ public class AssociationRequestsProcessor { @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice, boolean selfManaged, @Nullable IAssociationRequestCallback callback, @Nullable ResultReceiver resultReceiver) { - final int id = mAssociationStore.getNextId(userId); + final int id = mAssociationStore.getNextId(); final long timestamp = System.currentTimeMillis(); final AssociationInfo association = new AssociationInfo(id, userId, packageName, diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java index ae2b70852a35..29e8095f8680 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java @@ -132,7 +132,7 @@ public class AssociationStore { @GuardedBy("mLock") private final Map<Integer, AssociationInfo> mIdToAssociationMap = new HashMap<>(); @GuardedBy("mLock") - private final Map<Integer, Integer> mUserToMaxId = new HashMap<>(); + private int mMaxId = 0; @GuardedBy("mLocalListeners") private final Set<OnChangeListener> mLocalListeners = new LinkedHashSet<>(); @@ -162,7 +162,7 @@ public class AssociationStore { mPersisted = false; mIdToAssociationMap.clear(); - mUserToMaxId.clear(); + mMaxId = 0; // The data is stored in DE directories, so we can read the data for all users now // (which would not be possible if the data was stored to CE directories). @@ -172,7 +172,7 @@ public class AssociationStore { for (AssociationInfo association : entry.getValue().getAssociations()) { mIdToAssociationMap.put(association.getId(), association); } - mUserToMaxId.put(entry.getKey(), entry.getValue().getMaxId()); + mMaxId = Math.max(mMaxId, entry.getValue().getMaxId()); } mPersisted = true; @@ -183,18 +183,18 @@ public class AssociationStore { /** * Get the current max association id. */ - public int getMaxId(int userId) { + public int getMaxId() { synchronized (mLock) { - return mUserToMaxId.getOrDefault(userId, 0); + return mMaxId; } } /** * Get the next available association id. */ - public int getNextId(int userId) { + public int getNextId() { synchronized (mLock) { - return getMaxId(userId) + 1; + return getMaxId() + 1; } } @@ -214,7 +214,7 @@ public class AssociationStore { } mIdToAssociationMap.put(id, association); - mUserToMaxId.put(userId, Math.max(mUserToMaxId.getOrDefault(userId, 0), id)); + mMaxId = Math.max(mMaxId, id); writeCacheToDisk(userId); @@ -305,7 +305,7 @@ public class AssociationStore { mExecutor.execute(() -> { Associations associations = new Associations(); synchronized (mLock) { - associations.setMaxId(mUserToMaxId.getOrDefault(userId, 0)); + associations.setMaxId(mMaxId); associations.setAssociations( CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(), a -> a.getUserId() == userId)); diff --git a/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java index 3d610d3747c9..3d610d3747c9 100644 --- a/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java +++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index bdc4a7afae89..2545620a2630 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -22,6 +22,7 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS per-file *Battery* = file:/BATTERY_STATS_OWNERS per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS +per-file ExplicitHealthCheckController.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS @@ -35,9 +36,9 @@ per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationSer per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS per-file MmsServiceBroker.java = file:/telephony/OWNERS per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS -per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS +per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS -per-file RescueParty.java = shuc@google.com, ancr@google.com, harshitmahajan@google.com +per-file RescueParty.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS per-file SensitiveContentProtectionManagerService.java = file:/core/java/android/permission/OWNERS per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 75a8bdfe5416..6f20adf74ee2 100644 --- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -39,15 +39,15 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.LongArrayQueue; import android.util.Slog; import android.util.Xml; -import android.utils.BackgroundThread; -import android.utils.LongArrayQueue; -import android.utils.XmlUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index f86eb61c365f..271d552fc574 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -31,6 +31,7 @@ import android.content.pm.VersionedPackage; import android.crashrecovery.flags.Flags; import android.os.Build; import android.os.Environment; +import android.os.FileUtils; import android.os.PowerManager; import android.os.RecoverySystem; import android.os.SystemClock; @@ -43,11 +44,10 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Slog; -import android.utils.ArrayUtils; -import android.utils.FileUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.server.PackageWatchdog.FailureReasons; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 1e7bc397825c..6c7546eca50b 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.permission.flags.Flags.sensitiveContentImprovements; import static android.permission.flags.Flags.sensitiveNotificationAppProtection; import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS; import static android.view.flags.Flags.sensitiveContentAppProtection; @@ -24,6 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDI import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS; import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START; import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP; +import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -91,9 +93,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic private boolean mProjectionActive = false; private static class MediaProjectionSession { - final int mUid; - final long mSessionId; - final boolean mIsExempted; + private final int mUid; + private final long mSessionId; + private final boolean mIsExempted; + private final ArraySet<String> mAllSeenNotificationKeys = new ArraySet<>(); + private final ArraySet<String> mSeenOtpNotificationKeys = new ArraySet<>(); MediaProjectionSession(int uid, boolean isExempted, long sessionId) { mUid = uid; @@ -123,6 +127,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic ); } + public void logAppNotificationsProtected() { + FrameworkStatsLog.write( + SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION, + mSessionId, + mAllSeenNotificationKeys.size(), + mSeenOtpNotificationKeys.size()); + } + public void logAppBlocked(int uid) { FrameworkStatsLog.write( FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION, @@ -142,6 +154,32 @@ public final class SensitiveContentProtectionManagerService extends SystemServic FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED ); } + + private void addSeenNotificationKey(String key) { + mAllSeenNotificationKeys.add(key); + } + + private void addSeenOtpNotificationKey(String key) { + mAllSeenNotificationKeys.add(key); + mSeenOtpNotificationKeys.add(key); + } + + public void addSeenNotifications( + @NonNull StatusBarNotification[] notifications, + @NonNull RankingMap rankingMap) { + for (StatusBarNotification sbn : notifications) { + if (sbn == null) { + Log.w(TAG, "Unable to parse null notification"); + continue; + } + + if (notificationHasSensitiveContent(sbn, rankingMap)) { + addSeenOtpNotificationKey(sbn.getKey()); + } else { + addSeenNotificationKey(sbn.getKey()); + } + } + } } private final MediaProjectionManager.Callback mProjectionCallback = @@ -297,6 +335,9 @@ public final class SensitiveContentProtectionManagerService extends SystemServic mProjectionActive = false; if (mMediaProjectionSession != null) { mMediaProjectionSession.logProjectionSessionStop(); + if (sensitiveContentImprovements()) { + mMediaProjectionSession.logAppNotificationsProtected(); + } mMediaProjectionSession = null; } @@ -334,9 +375,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic notifications = new StatusBarNotification[0]; } + if (sensitiveContentImprovements() && mMediaProjectionSession != null) { + mMediaProjectionSession.addSeenNotifications(notifications, rankingMap); + } + // notify windowmanager of any currently posted sensitive content notifications ArraySet<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(notifications, rankingMap); + if (packageInfos.size() > 0) { mWindowManager.addBlockScreenCaptureForApps(packageInfos); } @@ -420,6 +466,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic mWindowManager.addBlockScreenCaptureForApps( new ArraySet(Set.of(packageInfo))); } + + if (sensitiveContentImprovements() && mMediaProjectionSession != null) { + if (packageInfo != null) { + mMediaProjectionSession.addSeenOtpNotificationKey(sbn.getKey()); + } else { + mMediaProjectionSession.addSeenNotificationKey(sbn.getKey()); + } + } } } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 77398476ddaa..9ed6e7e13f28 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -497,6 +497,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; +import java.time.Instant; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -683,8 +685,6 @@ public class ActivityManagerService extends IActivityManager.Stub public final IntentFirewall mIntentFirewall; - public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler(); - /** * The global lock for AMS, it's de-facto the ActivityManagerService object as of now. */ @@ -2596,7 +2596,6 @@ public class ActivityManagerService extends IActivityManager.Stub BackgroundThread.getHandler(), this); mOnBattery = DEBUG_POWER ? true : mBatteryStatsService.getActiveStatistics().getIsOnBattery(); - mOomAdjProfiler.batteryPowerChanged(mOnBattery); mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats")); @@ -2845,13 +2844,12 @@ public class ActivityManagerService extends IActivityManager.Stub updateCpuStatsNow(); synchronized (mProcLock) { mOnBattery = DEBUG_POWER ? true : onBattery; - mOomAdjProfiler.batteryPowerChanged(onBattery); } } @Override public void batteryStatsReset() { - mOomAdjProfiler.reset(); + // Empty for now. } @Override @@ -7417,7 +7415,6 @@ public class ActivityManagerService extends IActivityManager.Stub mServices.updateScreenStateLocked(isAwake); reportCurWakefulnessUsageEvent(); mActivityTaskManager.onScreenAwakeChanged(isAwake); - mOomAdjProfiler.onWakefulnessChanged(wakefulness); mOomAdjuster.onWakefulnessChanged(wakefulness); updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY); @@ -8949,8 +8946,10 @@ public class ActivityManagerService extends IActivityManager.Stub com.android.internal.R.integer.config_multiuserMaxRunningUsers); final boolean delayUserDataLocking = res.getBoolean( com.android.internal.R.bool.config_multiuserDelayUserDataLocking); + final int backgroundUserScheduledStopTimeSecs = res.getInteger( + com.android.internal.R.integer.config_backgroundUserScheduledStopTimeSecs); mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers, - delayUserDataLocking); + delayUserDataLocking, backgroundUserScheduledStopTimeSecs); } mAppErrors.loadAppsNotReportingCrashesFromConfig(res.getString( com.android.internal.R.string.config_appsNotReportingCrashes)); @@ -9845,6 +9844,11 @@ public class ActivityManagerService extends IActivityManager.Stub sb.append("Process-Runtime: ").append(runtimeMillis).append("\n"); } } + if (eventType.equals("crash")) { + String formattedTime = DROPBOX_TIME_FORMATTER.format( + Instant.now().atZone(ZoneId.systemDefault())); + sb.append("Timestamp: ").append(formattedTime).append("\n"); + } if (activityShortComponentName != null) { sb.append("Activity: ").append(activityShortComponentName).append("\n"); } @@ -10542,12 +10546,6 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println( "-------------------------------------------------------------------------------"); } - mOomAdjProfiler.dump(pw); - pw.println(); - if (dumpAll) { - pw.println( - "-------------------------------------------------------------------------------"); - } dumpLmkLocked(pw); } pw.println(); diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index ef015ee9d743..117221f6e1f3 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -334,6 +334,8 @@ public final class AppRestrictionController { final ActivityManagerService mActivityManagerService; + private volatile boolean mLockedBootCompleted = false; + static final int TRACKER_TYPE_UNKNOWN = 0; static final int TRACKER_TYPE_BATTERY = 1; static final int TRACKER_TYPE_BATTERY_EXEMPTION = 2; @@ -1721,8 +1723,10 @@ public final class AppRestrictionController { level = RESTRICTION_LEVEL_EXEMPTED; break; case STANDBY_BUCKET_NEVER: - level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; - break; + if (!android.app.Flags.appRestrictionsApi()) { + level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; + break; + } case STANDBY_BUCKET_ACTIVE: case STANDBY_BUCKET_WORKING_SET: case STANDBY_BUCKET_FREQUENT: @@ -1802,7 +1806,9 @@ public final class AppRestrictionController { case STANDBY_BUCKET_EXEMPTED: return RESTRICTION_LEVEL_EXEMPTED; case STANDBY_BUCKET_NEVER: - return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; + if (!android.app.Flags.appRestrictionsApi()) { + return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; + } case STANDBY_BUCKET_ACTIVE: case STANDBY_BUCKET_WORKING_SET: case STANDBY_BUCKET_FREQUENT: @@ -2214,7 +2220,8 @@ public final class AppRestrictionController { } } - if (doItNow && android.app.Flags.appRestrictionsApi()) { + if (doItNow && android.app.Flags.appRestrictionsApi() + && curLevel != RESTRICTION_LEVEL_UNKNOWN) { logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo, reason); } @@ -2308,6 +2315,9 @@ public final class AppRestrictionController { private void handleAppStandbyBucketChanged(int bucket, String packageName, @UserIdInt int userId) { + // Ignore spurious changes to standby bucket during early boot + if (android.app.Flags.appRestrictionsApi() && !mLockedBootCompleted) return; + final int uid = mInjector.getPackageManagerInternal().getPackageUid( packageName, STOCK_PM_FLAGS, userId); final Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevel( @@ -3391,6 +3401,7 @@ public final class AppRestrictionController { for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) { mAppStateTrackers.get(i).onLockedBootCompleted(); } + mLockedBootCompleted = true; } boolean isBgAutoRestrictedBucketFeatureFlagEnabled() { diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java deleted file mode 100644 index 08691145bf6e..000000000000 --- a/services/core/java/com/android/server/am/OomAdjProfiler.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2018 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.server.am; - -import android.os.Message; -import android.os.PowerManagerInternal; -import android.os.Process; -import android.os.SystemClock; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.BackgroundThread; -import com.android.internal.os.ProcessCpuTracker; -import com.android.internal.util.RingBuffer; -import com.android.internal.util.function.pooled.PooledLambda; - -import java.io.PrintWriter; - -public class OomAdjProfiler { - private static final int MSG_UPDATE_CPU_TIME = 42; - - @GuardedBy("this") - private boolean mOnBattery; - @GuardedBy("this") - private boolean mScreenOff; - - /** The value of {@link #mOnBattery} when the CPU time update was last scheduled. */ - @GuardedBy("this") - private boolean mLastScheduledOnBattery; - /** The value of {@link #mScreenOff} when the CPU time update was last scheduled. */ - @GuardedBy("this") - private boolean mLastScheduledScreenOff; - - @GuardedBy("this") - private long mOomAdjStartTimeUs; - @GuardedBy("this") - private boolean mOomAdjStarted; - - @GuardedBy("this") - private CpuTimes mOomAdjRunTime = new CpuTimes(); - @GuardedBy("this") - private CpuTimes mSystemServerCpuTime = new CpuTimes(); - - @GuardedBy("this") - private long mLastSystemServerCpuTimeMs; - @GuardedBy("this") - private boolean mSystemServerCpuTimeUpdateScheduled; - private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false); - - @GuardedBy("this") - final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10); - @GuardedBy("this") - final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10); - - @GuardedBy("this") - private long mTotalOomAdjRunTimeUs; - @GuardedBy("this") - private int mTotalOomAdjCalls; - - void batteryPowerChanged(boolean onBattery) { - synchronized (this) { - scheduleSystemServerCpuTimeUpdate(); - mOnBattery = onBattery; - } - } - - void onWakefulnessChanged(int wakefulness) { - synchronized (this) { - scheduleSystemServerCpuTimeUpdate(); - mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE; - } - } - - void oomAdjStarted() { - synchronized (this) { - mOomAdjStartTimeUs = SystemClock.currentThreadTimeMicro(); - mOomAdjStarted = true; - } - } - - void oomAdjEnded() { - synchronized (this) { - if (!mOomAdjStarted) { - return; - } - long elapsedUs = SystemClock.currentThreadTimeMicro() - mOomAdjStartTimeUs; - mOomAdjRunTime.addCpuTimeUs(elapsedUs); - mTotalOomAdjRunTimeUs += elapsedUs; - mTotalOomAdjCalls++; - } - } - - private void scheduleSystemServerCpuTimeUpdate() { - synchronized (this) { - if (mSystemServerCpuTimeUpdateScheduled) { - return; - } - mLastScheduledOnBattery = mOnBattery; - mLastScheduledScreenOff = mScreenOff; - mSystemServerCpuTimeUpdateScheduled = true; - Message scheduledMessage = PooledLambda.obtainMessage( - OomAdjProfiler::updateSystemServerCpuTime, - this, mLastScheduledOnBattery, mLastScheduledScreenOff, true); - scheduledMessage.setWhat(MSG_UPDATE_CPU_TIME); - - BackgroundThread.getHandler().sendMessage(scheduledMessage); - } - } - - private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff, - boolean onlyIfScheduled) { - final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid()); - synchronized (this) { - if (onlyIfScheduled && !mSystemServerCpuTimeUpdateScheduled) { - return; - } - mSystemServerCpuTime.addCpuTimeMs( - cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff); - mLastSystemServerCpuTimeMs = cpuTimeMs; - mSystemServerCpuTimeUpdateScheduled = false; - } - } - - void reset() { - synchronized (this) { - if (mSystemServerCpuTime.isEmpty()) { - return; - } - mOomAdjRunTimesHist.append(mOomAdjRunTime); - mSystemServerCpuTimesHist.append(mSystemServerCpuTime); - mOomAdjRunTime = new CpuTimes(); - mSystemServerCpuTime = new CpuTimes(); - } - } - - void dump(PrintWriter pw) { - synchronized (this) { - if (mSystemServerCpuTimeUpdateScheduled) { - // Cancel the scheduled update since we're going to update it here instead. - BackgroundThread.getHandler().removeMessages(MSG_UPDATE_CPU_TIME); - // Make sure the values are attributed to the right states. - updateSystemServerCpuTime(mLastScheduledOnBattery, mLastScheduledScreenOff, false); - } else { - updateSystemServerCpuTime(mOnBattery, mScreenOff, false); - } - - pw.println("System server and oomAdj runtimes (ms) in recent battery sessions " - + "(most recent first):"); - if (!mSystemServerCpuTime.isEmpty()) { - pw.print(" "); - pw.print("system_server="); - pw.print(mSystemServerCpuTime); - pw.print(" "); - pw.print("oom_adj="); - pw.println(mOomAdjRunTime); - } - final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray(); - final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray(); - for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) { - pw.print(" "); - pw.print("system_server="); - pw.print(systemServerCpuTimes[i]); - pw.print(" "); - pw.print("oom_adj="); - pw.println(oomAdjRunTimes[i]); - } - if (mTotalOomAdjCalls != 0) { - pw.println("System server total oomAdj runtimes (us) since boot:"); - pw.print(" cpu time spent="); - pw.print(mTotalOomAdjRunTimeUs); - pw.print(" number of calls="); - pw.print(mTotalOomAdjCalls); - pw.print(" average="); - pw.println(mTotalOomAdjRunTimeUs / mTotalOomAdjCalls); - } - } - } - - private class CpuTimes { - private long mOnBatteryTimeUs; - private long mOnBatteryScreenOffTimeUs; - - public void addCpuTimeMs(long cpuTimeMs) { - addCpuTimeUs(cpuTimeMs * 1000, mOnBattery, mScreenOff); - } - - public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) { - addCpuTimeUs(cpuTimeMs * 1000, onBattery, screenOff); - } - - public void addCpuTimeUs(long cpuTimeUs) { - addCpuTimeUs(cpuTimeUs, mOnBattery, mScreenOff); - } - - public void addCpuTimeUs(long cpuTimeUs, boolean onBattery, boolean screenOff) { - if (onBattery) { - mOnBatteryTimeUs += cpuTimeUs; - if (screenOff) { - mOnBatteryScreenOffTimeUs += cpuTimeUs; - } - } - } - - public boolean isEmpty() { - return mOnBatteryTimeUs == 0 && mOnBatteryScreenOffTimeUs == 0; - } - - public String toString() { - return "[" + (mOnBatteryTimeUs / 1000) + "," - + (mOnBatteryScreenOffTimeUs / 1000) + "]"; - } - } -} diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index ea7a21dd19cd..9b72db8f5d6f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -600,7 +600,6 @@ public class OomAdjuster { mLastReason = oomAdjReason; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); final ProcessStateRecord state = app.mState; @@ -630,7 +629,6 @@ public class OomAdjuster { } mTmpProcessList.clear(); mService.clearPendingTopAppLocked(); - mService.mOomAdjProfiler.oomAdjEnded(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return true; } @@ -849,7 +847,6 @@ public class OomAdjuster { mLastReason = oomAdjReason; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP(); final ArrayList<ProcessRecord> processes = mTmpProcessList; @@ -862,7 +859,6 @@ public class OomAdjuster { processes.clear(); mService.clearPendingTopAppLocked(); - mService.mOomAdjProfiler.oomAdjEnded(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -895,7 +891,6 @@ public class OomAdjuster { mLastReason = oomAdjReason; if (startProfiling) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); } final long now = SystemClock.uptimeMillis(); final long nowElapsed = SystemClock.elapsedRealtime(); @@ -989,7 +984,6 @@ public class OomAdjuster { postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true); if (startProfiling) { - mService.mOomAdjProfiler.oomAdjEnded(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 00e148287b67..3268b661df65 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -744,11 +744,9 @@ public class OomAdjusterModernImpl extends OomAdjuster { mLastReason = oomAdjReason; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); fullUpdateLSP(oomAdjReason); - mService.mOomAdjProfiler.oomAdjEnded(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -766,14 +764,12 @@ public class OomAdjusterModernImpl extends OomAdjuster { mLastReason = oomAdjReason; mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP(); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); synchronized (mProcLock) { partialUpdateLSP(oomAdjReason, mPendingProcessSet); } mPendingProcessSet.clear(); - mService.mOomAdjProfiler.oomAdjEnded(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index dd4cee47bee9..b7030765d053 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -59,6 +59,7 @@ import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOC import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER; import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED; import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE; +import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND; import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE; import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND; import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString; @@ -200,6 +201,7 @@ class UserController implements Handler.Callback { static final int START_USER_SWITCH_FG_MSG = 120; static final int COMPLETE_USER_SWITCH_MSG = 130; static final int USER_COMPLETED_EVENT_MSG = 140; + static final int SCHEDULED_STOP_BACKGROUND_USER_MSG = 150; private static final int NO_ARG2 = 0; @@ -251,6 +253,14 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private int mMaxRunningUsers; + /** + * Number of seconds of uptime after a full user enters the background before we attempt + * to stop it due to inactivity. Set to -1 to disable scheduling stopping background users. + * + * Typically set by config_backgroundUserScheduledStopTimeSecs. + */ + private int mBackgroundUserScheduledStopTimeSecs = -1; + // Lock for internal state. private final Object mLock = new Object(); @@ -453,11 +463,12 @@ class UserController implements Handler.Callback { } void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers, - boolean delayUserDataLocking) { + boolean delayUserDataLocking, int backgroundUserScheduledStopTimeSecs) { synchronized (mLock) { mUserSwitchUiEnabled = userSwitchUiEnabled; mMaxRunningUsers = maxRunningUsers; mDelayUserDataLocking = delayUserDataLocking; + mBackgroundUserScheduledStopTimeSecs = backgroundUserScheduledStopTimeSecs; mInitialized = true; } } @@ -1091,6 +1102,10 @@ class UserController implements Handler.Callback { final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) { Slogf.i(TAG, "stopSingleUserLU userId=" + userId); + if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) { + mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, + Integer.valueOf(userId)); + } final UserState uss = mStartedUsers.get(userId); if (uss == null) { // User is not started // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need @@ -1879,6 +1894,10 @@ class UserController implements Handler.Callback { // No matter what, the fact that we're requested to start the user (even if it is // already running) puts it towards the end of the mUserLru list. addUserToUserLru(userId); + if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) { + mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, + Integer.valueOf(userId)); + } if (unlockListener != null) { uss.mUnlockProgress.addListener(unlockListener); @@ -1923,6 +1942,9 @@ class UserController implements Handler.Callback { // of mUserLru, so we need to ensure that the foreground user isn't displaced. addUserToUserLru(mCurrentUserId); } + if (userStartMode == USER_START_MODE_BACKGROUND && !userInfo.isProfile()) { + scheduleStopOfBackgroundUser(userId); + } t.traceEnd(); // Make sure user is in the started state. If it is currently @@ -2294,6 +2316,65 @@ class UserController implements Handler.Callback { } } + /** + * Possibly schedules the user to be stopped at a future point. To be used to stop background + * users that haven't been actively used in a long time. + * This is only intended for full users that are currently in the background. + */ + private void scheduleStopOfBackgroundUser(@UserIdInt int oldUserId) { + if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) { + return; + } + final int delayUptimeSecs = mBackgroundUserScheduledStopTimeSecs; + if (delayUptimeSecs <= 0 || UserManager.isVisibleBackgroundUsersEnabled()) { + // Feature is not enabled on this device. + return; + } + if (oldUserId == UserHandle.USER_SYSTEM) { + // Never stop system user + return; + } + if (oldUserId == mInjector.getUserManagerInternal().getMainUserId()) { + // MainUser is currently special for things like Docking, so we'll exempt it for now. + Slogf.i(TAG, "Exempting user %d from being stopped due to inactivity by virtue " + + "of it being the main user", oldUserId); + return; + } + Slogf.d(TAG, "Scheduling to stop user %d in %d seconds", oldUserId, delayUptimeSecs); + final int delayUptimeMs = delayUptimeSecs * 1000; + final Object msgObj = oldUserId; + mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj), + delayUptimeMs); + } + + /** + * Possibly stops the given full user due to it having been in the background for a long time. + * There is no guarantee of stopping the user; it is done discretionarily. + * + * This should never be called for background visible users; devices that support this should + * not use {@link #scheduleStopOfBackgroundUser(int)}. + * + * @param userIdInteger a full user to be stopped if it is still in the background + */ + @VisibleForTesting + void processScheduledStopOfBackgroundUser(Integer userIdInteger) { + final int userId = userIdInteger; + Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId); + synchronized (mLock) { + if (getCurrentOrTargetUserIdLU() == userId) { + return; + } + if (mPendingTargetUserIds.contains(userIdInteger)) { + // We'll soon want to switch to this user, so don't kill it now. + return; + } + Slogf.i(TAG, "Stopping background user %d due to inactivity", userId); + stopUsersLU(userId, /* allowDelayedLocking= */ true, null, null); + } + } + private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId); @@ -2428,6 +2509,7 @@ class UserController implements Handler.Callback { uss.switching = false; stopGuestOrEphemeralUserIfBackground(oldUserId); stopUserOnSwitchIfEnforced(oldUserId); + scheduleStopOfBackgroundUser(oldUserId); t.traceEnd(); // end continueUserSwitch } @@ -3309,6 +3391,8 @@ class UserController implements Handler.Callback { pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); + pw.println(" mBackgroundUserScheduledStopTimeSecs:" + + mBackgroundUserScheduledStopTimeSecs); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); pw.println(" mIsBroadcastSentForSystemUserStarted:" @@ -3435,6 +3519,9 @@ class UserController implements Handler.Callback { case COMPLETE_USER_SWITCH_MSG: completeUserSwitch(msg.arg1, msg.arg2); break; + case SCHEDULED_STOP_BACKGROUND_USER_MSG: + processScheduledStopOfBackgroundUser((Integer) msg.obj); + break; } return false; } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index f38b38154bc3..9bdc51efb76f 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -987,9 +987,9 @@ public class AudioDeviceInventory { } if (di.mPeerDeviceAddress.equals("")) { for (Pair<String, String> addr : addresses) { - if (!addr.first.equals(di.mDeviceAddress)) { - di.mPeerDeviceAddress = addr.first; - di.mPeerIdentityDeviceAddress = addr.second; + if (!di.mDeviceAddress.equals(addr.first)) { + di.mPeerDeviceAddress = TextUtils.emptyIfNull(addr.first); + di.mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(addr.second); break; } } @@ -1000,8 +1000,8 @@ public class AudioDeviceInventory { } if (di.mDeviceIdentityAddress.equals("")) { for (Pair<String, String> addr : addresses) { - if (addr.first.equals(di.mDeviceAddress)) { - di.mDeviceIdentityAddress = addr.second; + if (di.mDeviceAddress.equals(addr.first)) { + di.mDeviceIdentityAddress = TextUtils.emptyIfNull(addr.second); break; } } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index edeabdc5243c..a649d34884a7 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -1110,6 +1110,12 @@ public class BtHelper { return mLeAudio.getGroupId(device); } + /** + * Returns all addresses and identity addresses for LE Audio devices a group. + * @param groupId The ID of the group from which to get addresses. + * @return A List of Pair(String main_address, String identity_address). Note that the + * addresses returned by BluetoothDevice can be null. + */ /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) { List<Pair<String, String>> addresses = new ArrayList<>(); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index df5f0079765c..68e2bd685fac 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -5271,13 +5271,5 @@ public final class DisplayManagerService extends SystemService { public ExternalDisplayStatsService getExternalDisplayStatsService() { return mExternalDisplayStatsService; } - - /** - * Called on external display is ready to be enabled. - */ - @Override - public void onExternalDisplayReadyToBeEnabled(int displayId) { - mDisplayModeDirector.onExternalDisplayReadyToBeEnabled(displayId); - } } } diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java index 3c2918f833ba..b24caf4ced76 100644 --- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java +++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java @@ -91,8 +91,6 @@ class ExternalDisplayPolicy { @NonNull ExternalDisplayStatsService getExternalDisplayStatsService(); - - void onExternalDisplayReadyToBeEnabled(int displayId); } @NonNull @@ -187,10 +185,6 @@ class ExternalDisplayPolicy { return; } - if (enabled) { - mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked()); - } - mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled); } @@ -223,7 +217,6 @@ class ExternalDisplayPolicy { if ((Build.IS_ENG || Build.IS_USERDEBUG) && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) { Slog.w(TAG, "External display is enabled by default, bypassing user consent."); - mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked()); mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED); return; } else { diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index ab0b3cbee3ad..fa423162985e 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -700,13 +700,6 @@ public class DisplayModeDirector { } /** - * Called when external display is ready to be enabled. - */ - public void onExternalDisplayReadyToBeEnabled(int displayId) { - mDisplayObserver.onExternalDisplayReadyToBeEnabled(displayId); - } - - /** * Provides access to DisplayDeviceConfig for specific display */ public interface DisplayDeviceConfigProvider { @@ -1110,20 +1103,6 @@ public class DisplayModeDirector { if (Float.isInfinite(minRefreshRate)) { // Infinity means that we want the highest possible refresh rate minRefreshRate = highestRefreshRate; - - if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled - && displayId == Display.DEFAULT_DISPLAY) { - // The flag has been turned off, we need to restore the original value. We'll - // use the peak refresh rate of the default display. - Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, - highestRefreshRate, cr.getUserId()); - } - } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled - && displayId == Display.DEFAULT_DISPLAY - && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) { - // The flag has been turned on, we need to upgrade the setting - Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, - Float.POSITIVE_INFINITY, cr.getUserId()); } float peakRefreshRate = Settings.System.getFloatForUser(cr, @@ -1131,20 +1110,6 @@ public class DisplayModeDirector { if (Float.isInfinite(peakRefreshRate)) { // Infinity means that we want the highest possible refresh rate peakRefreshRate = highestRefreshRate; - - if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled - && displayId == Display.DEFAULT_DISPLAY) { - // The flag has been turned off, we need to restore the original value. We'll - // use the peak refresh rate of the default display. - Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE, - highestRefreshRate, cr.getUserId()); - } - } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled - && displayId == Display.DEFAULT_DISPLAY - && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) { - // The flag has been turned on, we need to upgrade the setting - Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE, - Float.POSITIVE_INFINITY, cr.getUserId()); } updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate, @@ -1400,13 +1365,6 @@ public class DisplayModeDirector { } } - - void onExternalDisplayReadyToBeEnabled(int displayId) { - DisplayInfo displayInfo = getDisplayInfo(displayId); - updateDisplaysPeakRefreshRateAndResolution(displayInfo); - addDisplaysSynchronizedPeakRefreshRate(displayInfo); - } - @Override public void onDisplayAdded(int displayId) { updateDisplayDeviceConfig(displayId); @@ -1414,6 +1372,8 @@ public class DisplayModeDirector { updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); updateUserSettingDisplayPreferredSize(displayInfo); + updateDisplaysPeakRefreshRateAndResolution(displayInfo); + addDisplaysSynchronizedPeakRefreshRate(displayInfo); } @Override diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 8df38a8d565a..c105b9c37026 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -473,6 +473,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { .setProviderId(mUniqueId) .setSystemSession(true) .addSelectedRoute(MediaRoute2Info.ROUTE_ID_DEFAULT) + .setTransferReason(newSessionInfo.getTransferReason()) + .setTransferInitiator( + newSessionInfo.getTransferInitiatorUserHandle(), + newSessionInfo.getTransferInitiatorPackageName()) .build(); return true; } diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java index 5ebcca875d86..2c1453261808 100644 --- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java @@ -275,10 +275,7 @@ public class PersistentDataBlockService extends SystemService { if (mFrpEnforced) { automaticallyDeactivateFrpIfPossible(); setOemUnlockEnabledProperty(doGetOemUnlockEnabled()); - // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it. - // They should switch to calling #isFrpActive(). - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.SECURE_FRP_MODE, mFrpActive ? 1 : 0); + setOldSettingForBackworkCompatibility(mFrpActive); } else { formatIfOemUnlockEnabled(); } @@ -292,6 +289,13 @@ public class PersistentDataBlockService extends SystemService { mInitDoneSignal.countDown(); } + private void setOldSettingForBackworkCompatibility(boolean isActive) { + // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it. + // They should switch to calling #isFrpActive(). + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.SECURE_FRP_MODE, isActive ? 1 : 0); + } + private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) { setProperty(OEM_UNLOCK_PROP, oemUnlockEnabled ? "1" : "0"); } @@ -628,6 +632,7 @@ public class PersistentDataBlockService extends SystemService { Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret"); writeFrpMagicAndDefaultSecret(); mFrpActive = false; + setOldSettingForBackworkCompatibility(mFrpActive); return true; } @@ -699,6 +704,7 @@ public class PersistentDataBlockService extends SystemService { void activateFrp() { synchronized (mLock) { mFrpActive = true; + setOldSettingForBackworkCompatibility(mFrpActive); } } @@ -740,6 +746,7 @@ public class PersistentDataBlockService extends SystemService { if (MessageDigest.isEqual(secret, partitionSecret)) { mFrpActive = false; Slog.i(TAG, "FRP secret matched, FRP deactivated."); + setOldSettingForBackworkCompatibility(mFrpActive); return true; } else { Slog.e(TAG, @@ -1315,6 +1322,7 @@ public class PersistentDataBlockService extends SystemService { public boolean deactivateFactoryResetProtectionWithoutSecret() { synchronized (mLock) { mFrpActive = false; + setOldSettingForBackworkCompatibility(/* isActive */ mFrpActive); } return true; } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index bd0501d920c4..20c5b5f8a308 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -462,6 +462,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override + public int getNumRegisteredAttributionSources(int uid) { + return mAttributionSourceRegistry.getNumRegisteredAttributionSources(uid); + } + + @Override public List<String> getAutoRevokeExemptionRequestedPackages(int userId) { return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISCOURAGED, userId); } @@ -938,6 +943,26 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } + public int getNumRegisteredAttributionSources(int uid) { + mContext.enforceCallingOrSelfPermission(UPDATE_APP_OPS_STATS, + "getting the number of registered AttributionSources requires " + + "UPDATE_APP_OPS_STATS"); + // Influence the system to perform a garbage collection, so the provided number is as + // accurate as possible + System.gc(); + System.gc(); + synchronized (mLock) { + int[] numForUid = { 0 }; + mAttributions.forEach((key, value) -> { + if (value.getUid() == uid) { + numForUid[0]++; + } + + }); + return numForUid[0]; + } + } + private int resolveUid(int uid) { final VoiceInteractionManagerInternal vimi = LocalServices .getService(VoiceInteractionManagerInternal.class); diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 93f26aefb692..93f26aefb692 100644 --- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java index 519c0edfc532..519c0edfc532 100644 --- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 5b501e16d652..587be0746149 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -16,6 +16,7 @@ package com.android.server.security; +import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; @@ -27,6 +28,7 @@ import android.os.Build; import android.os.Environment; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.PermissionEnforcer; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -79,7 +81,11 @@ public class FileIntegrityService extends SystemService { return LocalServices.getService(FileIntegrityService.class); } - private final IBinder mService = new IFileIntegrityService.Stub() { + private final class BinderService extends IFileIntegrityService.Stub { + BinderService(Context context) { + super(PermissionEnforcer.fromContext(context)); + } + @Override public boolean isApkVeritySupported() { return VerityUtils.isFsVeritySupported(); @@ -168,12 +174,10 @@ public class FileIntegrityService extends SystemService { } @Override + @EnforcePermission(android.Manifest.permission.SETUP_FSVERITY) public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken, String filePath, String packageName) throws RemoteException { - getContext().enforceCallingPermission(android.Manifest.permission.SETUP_FSVERITY, - "Permission android.permission.SETUP_FSVERITY not grantted to access " - + "FileIntegrityManager#setupFsverity"); - + setupFsverity_enforcePermission(); Objects.requireNonNull(authToken); Objects.requireNonNull(filePath); Objects.requireNonNull(packageName); @@ -185,10 +189,12 @@ public class FileIntegrityService extends SystemService { throw new RemoteException(e); } } - }; + } + private final IBinder mService; public FileIntegrityService(final Context context) { super(context); + mService = new BinderService(context); try { sCertFactory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java index e89ddfd2627c..34d18cecf909 100644 --- a/services/core/java/com/android/server/selinux/QuotaLimiter.java +++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java @@ -34,10 +34,10 @@ public class QuotaLimiter { private final Clock mClock; private final Duration mWindowSize; - private final int mMaxPermits; - private long mCurrentWindow = 0; - private int mPermitsGranted = 0; + private int mMaxPermits; + private long mCurrentWindow; + private int mPermitsGranted; @VisibleForTesting QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) { @@ -75,4 +75,8 @@ public class QuotaLimiter { return false; } + + public void setMaxPermits(int maxPermits) { + this.mMaxPermits = maxPermits; + } } diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java index 8d8d5960038e..d69150d88e4f 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java @@ -15,35 +15,66 @@ */ package com.android.server.selinux; +import android.provider.DeviceConfig; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + import java.util.Arrays; import java.util.Iterator; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import java.util.stream.Stream; /** Builder for SelinuxAuditLogs. */ class SelinuxAuditLogBuilder { - // Currently logs collection is hardcoded for the sdk_sandbox_audit. - private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit"; - static final Matcher SCONTEXT_MATCHER = - Pattern.compile( - "u:r:(?<stype>" - + SDK_SANDBOX_AUDIT - + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*") - .matcher(""); + private static final String TAG = "SelinuxAuditLogs"; - static final Matcher TCONTEXT_MATCHER = - Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*") - .matcher(""); + // This config indicates which Selinux logs for source domains to collect. The string will be + // inserted into a regex, so it must follow the regex syntax. For example, a valid value would + // be "system_server|untrusted_app". + @VisibleForTesting static final String CONFIG_SELINUX_AUDIT_DOMAIN = "selinux_audit_domain"; + private static final Matcher NO_OP_MATCHER = Pattern.compile("no-op^").matcher(""); + private static final String TCONTEXT_PATTERN = + "u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*"; + private static final String PATH_PATTERN = "\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\""; - static final Matcher PATH_MATCHER = - Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher(""); + @VisibleForTesting final Matcher mScontextMatcher; + @VisibleForTesting final Matcher mTcontextMatcher; + @VisibleForTesting final Matcher mPathMatcher; private Iterator<String> mTokens; private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog(); + SelinuxAuditLogBuilder() { + Matcher scontextMatcher = NO_OP_MATCHER; + Matcher tcontextMatcher = NO_OP_MATCHER; + Matcher pathMatcher = NO_OP_MATCHER; + try { + scontextMatcher = + Pattern.compile( + TextUtils.formatSimple( + "u:r:(?<stype>%s):s0(:c)?(?<scategories>((,c)?\\d+)+)*", + DeviceConfig.getString( + DeviceConfig.NAMESPACE_ADSERVICES, + CONFIG_SELINUX_AUDIT_DOMAIN, + "no_match^"))) + .matcher(""); + tcontextMatcher = Pattern.compile(TCONTEXT_PATTERN).matcher(""); + pathMatcher = Pattern.compile(PATH_PATTERN).matcher(""); + } catch (PatternSyntaxException e) { + Slog.e(TAG, "Invalid pattern, setting every matcher to no-op.", e); + } + + mScontextMatcher = scontextMatcher; + mTcontextMatcher = tcontextMatcher; + mPathMatcher = pathMatcher; + } + void reset(String denialString) { mTokens = Arrays.asList( @@ -82,18 +113,18 @@ class SelinuxAuditLogBuilder { mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new); break; case "scontext": - if (!nextTokenMatches(SCONTEXT_MATCHER)) { + if (!nextTokenMatches(mScontextMatcher)) { return null; } - mAuditLog.mSType = SCONTEXT_MATCHER.group("stype"); - mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories")); + mAuditLog.mSType = mScontextMatcher.group("stype"); + mAuditLog.mSCategories = toCategories(mScontextMatcher.group("scategories")); break; case "tcontext": - if (!nextTokenMatches(TCONTEXT_MATCHER)) { + if (!nextTokenMatches(mTcontextMatcher)) { return null; } - mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype"); - mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories")); + mAuditLog.mTType = mTcontextMatcher.group("ttype"); + mAuditLog.mTCategories = toCategories(mTcontextMatcher.group("tcategories")); break; case "tclass": if (!mTokens.hasNext()) { @@ -102,8 +133,8 @@ class SelinuxAuditLogBuilder { mAuditLog.mTClass = mTokens.next(); break; case "path": - if (nextTokenMatches(PATH_MATCHER)) { - mAuditLog.mPath = PATH_MATCHER.group("path"); + if (nextTokenMatches(mPathMatcher)) { + mAuditLog.mPath = mPathMatcher.group("path"); } break; case "permissive": diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java index 03822aaf76b2..c655d46eb9f4 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java @@ -18,10 +18,12 @@ package com.android.server.selinux; import android.util.EventLog; import android.util.EventLog.Event; import android.util.Log; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog; +import com.android.server.utils.Slogf; import java.io.IOException; import java.time.Instant; @@ -37,6 +39,7 @@ import java.util.regex.Pattern; class SelinuxAuditLogsCollector { private static final String TAG = "SelinuxAuditLogs"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$"; @@ -48,13 +51,17 @@ class SelinuxAuditLogsCollector { @VisibleForTesting Instant mLastWrite = Instant.MIN; - final AtomicBoolean mStopRequested = new AtomicBoolean(false); + AtomicBoolean mStopRequested = new AtomicBoolean(false); SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) { mRateLimiter = rateLimiter; mQuotaLimiter = quotaLimiter; } + public void setStopRequested(boolean stopRequested) { + mStopRequested.set(stopRequested); + } + /** * Collect and push SELinux audit logs for the provided {@code tagCode}. * @@ -66,7 +73,7 @@ class SelinuxAuditLogsCollector { boolean quotaExceeded = writeAuditLogs(logLines); if (quotaExceeded) { - Log.w(TAG, "Too many SELinux logs in the queue, I am giving up."); + Slog.w(TAG, "Too many SELinux logs in the queue, I am giving up."); mLastWrite = latestTimestamp; // next run we will ignore all these logs. logLines.clear(); } @@ -79,7 +86,7 @@ class SelinuxAuditLogsCollector { try { EventLog.readEvents(new int[] {tagCode}, events); } catch (IOException e) { - Log.e(TAG, "Error reading event logs", e); + Slog.e(TAG, "Error reading event logs", e); } Instant latestTimestamp = mLastWrite; @@ -102,6 +109,7 @@ class SelinuxAuditLogsCollector { private boolean writeAuditLogs(Queue<Event> logLines) { final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder(); + int auditsWritten = 0; while (!mStopRequested.get() && !logLines.isEmpty()) { Event event = logLines.poll(); @@ -118,6 +126,9 @@ class SelinuxAuditLogsCollector { } if (!mQuotaLimiter.acquire()) { + if (DEBUG) { + Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten); + } return true; } mRateLimiter.acquire(); @@ -133,12 +144,16 @@ class SelinuxAuditLogsCollector { auditLog.mTClass, auditLog.mPath, auditLog.mPermissive); + auditsWritten++; if (logTime.isAfter(mLastWrite)) { mLastWrite = logTime; } } + if (DEBUG) { + Slogf.d(TAG, "Written %d logs", auditsWritten); + } return false; } } diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java new file mode 100644 index 000000000000..0092c3797156 --- /dev/null +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 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.server.selinux; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.util.Slog; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class handles the start and stop requests for the logs collector job, in particular making + * sure that at most one job is running at any given moment. + */ +final class SelinuxAuditLogsJob { + + private static final String TAG = "SelinuxAuditLogs"; + + private final AtomicBoolean mIsRunning = new AtomicBoolean(false); + private final SelinuxAuditLogsCollector mAuditLogsCollector; + + SelinuxAuditLogsJob(SelinuxAuditLogsCollector auditLogsCollector) { + mAuditLogsCollector = auditLogsCollector; + } + + void requestStop() { + mAuditLogsCollector.mStopRequested.set(true); + } + + boolean isRunning() { + return mIsRunning.get(); + } + + public void start(JobService jobService, JobParameters params) { + mAuditLogsCollector.mStopRequested.set(false); + if (mIsRunning.get()) { + Slog.i(TAG, "Selinux audit job is already running, ignore start request."); + return; + } + mIsRunning.set(true); + boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE); + if (done) { + jobService.jobFinished(params, /* wantsReschedule= */ false); + } + mIsRunning.set(false); + } +} diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java index 8a661bcc13af..d46e8916d9e9 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java @@ -23,14 +23,16 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; import android.util.EventLog; -import android.util.Log; +import android.util.Slog; import java.time.Duration; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; /** * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle @@ -43,58 +45,68 @@ public class SelinuxAuditLogsService extends JobService { static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd"); + private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS = + "selinux_audit_job_frequency_hours"; + private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job"; + private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap"; + private static final int MAX_PERMITS_CAP_DEFAULT = 50000; + private static final int SELINUX_AUDIT_JOB_ID = 25327386; - private static final JobInfo SELINUX_AUDIT_JOB = - new JobInfo.Builder( - SELINUX_AUDIT_JOB_ID, - new ComponentName("android", SelinuxAuditLogsService.class.getName())) - .setPeriodic(TimeUnit.DAYS.toMillis(1)) - .setRequiresDeviceIdle(true) - .setRequiresCharging(true) - .setRequiresBatteryNotLow(true) - .build(); + private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT = + new ComponentName("android", SelinuxAuditLogsService.class.getName()); private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor(); - private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false); - // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10 - // milliseconds, and no more than 50K atoms can be pushed each day. - private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR = - new SelinuxAuditLogsCollector( - new RateLimiter(/* window= */ Duration.ofMillis(10)), - new QuotaLimiter(/* maxPermitsPerDay= */ 50000)); + // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure + // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the + // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static + // because new job executions happen in a new instance of this class. Making the quota limiter + // an instance reference would reset the quota limitations between jobs executions. + private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10); + private static final QuotaLimiter QUOTA_LIMITER = + new QuotaLimiter( + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ADSERVICES, + CONFIG_SELINUX_AUDIT_CAP, + MAX_PERMITS_CAP_DEFAULT)); + private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB = + new SelinuxAuditLogsJob( + new SelinuxAuditLogsCollector( + new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER)); /** Schedule jobs with the {@link JobScheduler}. */ public static void schedule(Context context) { if (!selinuxSdkSandboxAudit()) { - Log.d(TAG, "SelinuxAuditLogsService not enabled"); + Slog.d(TAG, "SelinuxAuditLogsService not enabled"); return; } if (AUDITD_TAG_CODE == -1) { - Log.e(TAG, "auditd is not a registered tag on this system"); + Slog.e(TAG, "auditd is not a registered tag on this system"); return; } - if (context.getSystemService(JobScheduler.class) - .forNamespace(SELINUX_AUDIT_NAMESPACE) - .schedule(SELINUX_AUDIT_JOB) - == JobScheduler.RESULT_FAILURE) { - Log.e(TAG, "SelinuxAuditLogsService could not be started."); - } + LogsCollectorJobScheduler propertiesListener = + new LogsCollectorJobScheduler( + context.getSystemService(JobScheduler.class) + .forNamespace(SELINUX_AUDIT_NAMESPACE)); + propertiesListener.schedule(); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener); } @Override public boolean onStartJob(JobParameters params) { if (params.getJobId() != SELINUX_AUDIT_JOB_ID) { - Log.e(TAG, "The job id does not match the expected selinux job id."); + Slog.e(TAG, "The job id does not match the expected selinux job id."); + return false; + } + if (!selinuxSdkSandboxAudit()) { + Slog.i(TAG, "Selinux audit job disabled."); return false; } - AUDIT_LOGS_COLLECTOR.mStopRequested.set(false); - IS_RUNNING.set(true); - EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params)); - + EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params)); return true; // the job is running } @@ -104,29 +116,69 @@ public class SelinuxAuditLogsService extends JobService { return false; } - AUDIT_LOGS_COLLECTOR.mStopRequested.set(true); - return IS_RUNNING.get(); + if (LOGS_COLLECTOR_JOB.isRunning()) { + LOGS_COLLECTOR_JOB.requestStop(); + return true; + } + return false; } - private static class LogsCollectorJob implements Runnable { - private final JobService mAuditLogService; - private final JobParameters mParams; + /** + * This class is in charge of scheduling the job service, and keeping the scheduling up to date + * when the parameters change. + */ + private static final class LogsCollectorJobScheduler + implements DeviceConfig.OnPropertiesChangedListener { + + private final JobScheduler mJobScheduler; - LogsCollectorJob(JobService auditLogService, JobParameters params) { - mAuditLogService = auditLogService; - mParams = params; + private LogsCollectorJobScheduler(JobScheduler jobScheduler) { + mJobScheduler = jobScheduler; } @Override - public void run() { - IS_RUNNING.updateAndGet( - isRunning -> { - boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE); - if (done) { - mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false); - } - return !done; - }); + public void onPropertiesChanged(Properties changedProperties) { + Set<String> keyset = changedProperties.getKeyset(); + + if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) { + QUOTA_LIMITER.setMaxPermits( + changedProperties.getInt( + CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT)); + } + + if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) { + boolean enabled = + changedProperties.getBoolean( + CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false); + if (enabled) { + schedule(); + } else { + mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID); + } + } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) { + // The job frequency changed, reschedule. + schedule(); + } + } + + private void schedule() { + long frequencyMillis = + TimeUnit.HOURS.toMillis( + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ADSERVICES, + CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS, + 24)); + if (mJobScheduler.schedule( + new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT) + .setPeriodic(frequencyMillis) + .setRequiresDeviceIdle(true) + .setRequiresBatteryNotLow(true) + .build()) + == JobScheduler.RESULT_FAILURE) { + Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled."); + } else { + Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully."); + } } } } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java index 4a81c95f0b8f..440d2514537c 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java @@ -89,8 +89,34 @@ public class CasResource { * @param ownerId the removing client id of the owner. */ public void removeOwner(int ownerId) { - mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId); - mOwnerClientIdsToSessionNum.remove(ownerId); + if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) { + mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId); + mOwnerClientIdsToSessionNum.remove(ownerId); + } + } + + /** + * Remove a single session from resource + * + * @param ownerId the client Id of the owner of the session + */ + public void removeSession(int ownerId) { + if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) { + int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId); + if (sessionNum > 0) { + mOwnerClientIdsToSessionNum.put(ownerId, --sessionNum); + mAvailableSessionNum++; + } + } + } + + /** + * Check if there are any open sessions owned by a client + * + * @param ownerId the client Id of the owner of the sessions + */ + public boolean hasOpenSessions(int ownerId) { + return mOwnerClientIdsToSessionNum.get(ownerId) > 0; } public Set<Integer> getOwnerClientIds() { diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index cddc79db6106..0afb049d31c7 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -1924,11 +1924,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde ownerProfile.useCiCam(grantingId); } - private void updateCasClientMappingOnRelease( - @NonNull CasResource releasingCas, int ownerClientId) { - ClientProfile ownerProfile = getClientProfile(ownerClientId); - releasingCas.removeOwner(ownerClientId); - ownerProfile.releaseCas(); + private void updateCasClientMappingOnRelease(@NonNull CasResource cas, int ownerClientId) { + cas.removeSession(ownerClientId); + if (!cas.hasOpenSessions(ownerClientId)) { + ClientProfile ownerProfile = getClientProfile(ownerClientId); + cas.removeOwner(ownerClientId); + ownerProfile.releaseCas(); + } } private void updateCiCamClientMappingOnRelease( diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java index b7d8cfce34ed..e944eca63144 100644 --- a/services/core/java/com/android/server/utils/AnrTimer.java +++ b/services/core/java/com/android/server/utils/AnrTimer.java @@ -193,6 +193,10 @@ public class AnrTimer<V> implements AutoCloseable { @GuardedBy("mLock") private int mTotalStarted = 0; + /** The total number of timers that were restarted without an explicit cancel. */ + @GuardedBy("mLock") + private int mTotalRestarted = 0; + /** The total number of errors detected. */ @GuardedBy("mLock") private int mTotalErrors = 0; @@ -434,10 +438,10 @@ public class AnrTimer<V> implements AutoCloseable { @Override void start(@NonNull V arg, int pid, int uid, long timeoutMs) { synchronized (mLock) { - if (mTimerIdMap.containsKey(arg)) { - // There is an existing timer. Cancel it. - cancel(arg); - } + // If there is an existing timer, cancel it. This is a nop if the timer does not + // exist. + if (cancel(arg)) mTotalRestarted++; + int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs, mExtend); if (timerId > 0) { mTimerIdMap.put(arg, timerId); @@ -546,9 +550,7 @@ public class AnrTimer<V> implements AutoCloseable { private Integer removeLocked(V arg) { Integer r = mTimerIdMap.remove(arg); if (r != null) { - synchronized (mTimerArgMap) { - mTimerArgMap.remove(r); - } + mTimerArgMap.remove(r); } return r; } @@ -672,8 +674,8 @@ public class AnrTimer<V> implements AutoCloseable { synchronized (mLock) { pw.format("timer: %s\n", mLabel); pw.increaseIndent(); - pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n", - mTotalStarted, mMaxStarted, mTimerIdMap.size(), + pw.format("started=%d maxStarted=%d restarted=%d running=%d expired=%d errors=%d\n", + mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(), mTotalExpired, mTotalErrors); pw.decreaseIndent(); mFeature.dump(pw, false); diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java index b33fa6f56a23..f82ff673d74e 100644 --- a/services/core/java/com/android/server/vibrator/VibratorControlService.java +++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java @@ -101,7 +101,9 @@ final class VibratorControlService extends IVibratorControlService.Stub { } @Override - public void registerVibratorController(IVibratorController controller) { + public void registerVibratorController(@NonNull IVibratorController controller) { + Objects.requireNonNull(controller); + synchronized (mLock) { mVibratorControllerHolder.setVibratorController(controller); } @@ -134,6 +136,7 @@ final class VibratorControlService extends IVibratorControlService.Stub { public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params, @NonNull IVibratorController token) { Objects.requireNonNull(token); + requireContainsNoNullElement(params); synchronized (mLock) { if (mVibratorControllerHolder.getVibratorController() == null) { @@ -148,6 +151,13 @@ final class VibratorControlService extends IVibratorControlService.Stub { + "controller doesn't match the registered one. " + this); return; } + if (params == null) { + // Adaptive haptics scales cannot be set to null. Ignoring request. + Slog.d(TAG, + "New vibration params received but are null. New vibration " + + "params ignored."); + return; + } updateAdaptiveHapticsScales(params); recordUpdateVibrationParams(params, /* fromRequest= */ false); @@ -181,6 +191,7 @@ final class VibratorControlService extends IVibratorControlService.Stub { public void onRequestVibrationParamsComplete( @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) { Objects.requireNonNull(requestToken); + requireContainsNoNullElement(result); synchronized (mLock) { if (mVibrationParamRequest == null) { @@ -202,6 +213,13 @@ final class VibratorControlService extends IVibratorControlService.Stub { long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs; mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs); + if (result == null) { + Slog.d(TAG, + "New vibration params received but are null. New vibration " + + "params ignored."); + return; + } + updateAdaptiveHapticsScales(result); endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false); recordUpdateVibrationParams(result, /* fromRequest= */ true); @@ -401,10 +419,9 @@ final class VibratorControlService extends IVibratorControlService.Stub { * * @param params the new vibration params. */ - private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) { - if (params == null) { - return; - } + private void updateAdaptiveHapticsScales(@NonNull VibrationParam[] params) { + Objects.requireNonNull(params); + for (VibrationParam param : params) { if (param.getTag() != VibrationParam.scale) { Slog.e(TAG, "Unsupported vibration param: " + param); @@ -448,11 +465,10 @@ final class VibratorControlService extends IVibratorControlService.Stub { mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale); } - private void recordUpdateVibrationParams(@Nullable VibrationParam[] params, + private void recordUpdateVibrationParams(@NonNull VibrationParam[] params, boolean fromRequest) { - if (params == null) { - return; - } + Objects.requireNonNull(params); + VibrationParamsRecords.Operation operation = fromRequest ? VibrationParamsRecords.Operation.PULL : VibrationParamsRecords.Operation.PUSH; @@ -474,6 +490,13 @@ final class VibratorControlService extends IVibratorControlService.Stub { VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE)); } + private void requireContainsNoNullElement(VibrationParam[] params) { + if (ArrayUtils.contains(params, null)) { + throw new IllegalArgumentException( + "Invalid vibration params received: null values are not permitted."); + } + } + /** * Keep records of {@link VibrationParam} values received by this service from a registered * {@link VibratorController} and provide debug information for this service. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index bf094ed57545..06003e4b910a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -658,6 +658,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private CompatDisplayInsets mCompatDisplayInsets; + private final TaskFragment.ConfigOverrideHint mResolveConfigHint; + private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig; boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session @@ -2110,6 +2112,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLetterboxUiController = new LetterboxUiController(mWmService, this); mCameraCompatControlEnabled = mWmService.mContext.getResources() .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); + mResolveConfigHint = new TaskFragment.ConfigOverrideHint(); + mResolveConfigHint.mUseLegacyInsetsForStableBounds = + mWmService.mFlags.mInsetsDecoupledConfiguration + && !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED); mTargetSdk = info.applicationInfo.targetSdkVersion; @@ -8439,7 +8445,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The role of CompatDisplayInsets is like the override bounds. mCompatDisplayInsets = new CompatDisplayInsets( - mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio); + mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio, + mResolveConfigHint.mUseLegacyInsetsForStableBounds); } private void clearSizeCompatModeAttributes() { @@ -8546,8 +8553,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If the activity has requested override bounds, the configuration needs to be // computed accordingly. if (!matchParentBounds()) { - getTaskFragment().computeConfigResourceOverrides(resolvedConfig, - newParentConfiguration); + computeConfigByResolveHint(resolvedConfig, newParentConfiguration); } // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. @@ -8636,16 +8642,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mDisplayContent == null) { return; } - final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds(); + final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); int rotation = newParentConfiguration.windowConfiguration.getRotation(); if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) { rotation = mDisplayContent.getRotation(); } - if (!mWmService.mFlags.mInsetsDecoupledConfiguration - || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) + if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds || getCompatDisplayInsets() != null - || isFloating(parentWindowingMode) || fullBounds == null - || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) { + || isFloating(parentWindowingMode) || parentAppBounds == null + || parentAppBounds.isEmpty() || rotation == ROTATION_UNDEFINED) { // If the insets configuration decoupled logic is not enabled for the app, or the app // already has a compat override, or the context doesn't contain enough info to // calculate the override, skip the override. @@ -8653,15 +8658,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Override starts here. - final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo( - rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets; + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated ? mDisplayContent.mBaseDisplayHeight + : mDisplayContent.mBaseDisplayWidth; + final int dh = rotated ? mDisplayContent.mBaseDisplayWidth + : mDisplayContent.mBaseDisplayHeight; + final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy() + .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets; // This should be the only place override the configuration for ActivityRecord. Override // the value if not calculated yet. Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); if (outAppBounds == null || outAppBounds.isEmpty()) { - inOutConfig.windowConfiguration.setAppBounds(fullBounds); + inOutConfig.windowConfiguration.setAppBounds(parentAppBounds); outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - outAppBounds.inset(stableInsets); + outAppBounds.inset(nonDecorInsets); } float density = inOutConfig.densityDpi; if (density == Configuration.DENSITY_DPI_UNDEFINED) { @@ -8685,11 +8695,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // For the case of PIP transition and multi-window environment, the // smallestScreenWidthDp is handled already. Override only if the app is in // fullscreen. - final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); DisplayInfo info = new DisplayInfo(); mDisplayContent.getDisplay().getDisplayInfo(info); - mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth, - info.logicalHeight, mDisplayContent.getDisplayMetrics().density, + mDisplayContent.computeSizeRanges(info, rotated, dw, dh, + mDisplayContent.getDisplayMetrics().density, inOutConfig, true /* overrideConfig */); } @@ -8702,14 +8711,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - /** - * @return The orientation to use to understand if reachability is enabled. - */ - @Configuration.Orientation - int getOrientationForReachability() { - return mLetterboxUiController.hasInheritedLetterboxBehavior() - ? mLetterboxUiController.getInheritedOrientation() - : getRequestedConfigurationOrientation(); + private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig, + @NonNull Configuration parentConfig) { + task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint); + // Reset the temp info which should only take effect for the specified computation. + mResolveConfigHint.mTmpCompatInsets = null; + mResolveConfigHint.mTmpOverrideDisplayInfo = null; } /** @@ -8852,7 +8859,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Since bounds has changed, the configuration needs to be computed accordingly. - getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + computeConfigByResolveHint(resolvedConfig, newParentConfiguration); // The position of configuration bounds were calculated in screen space because that is // easier to resolve the relative position in parent container. However, if the activity is @@ -8946,17 +8953,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * to compute the stable bounds. * @param outStableBounds will store the stable bounds, which are the bounds with insets * applied, if orientation is not respected when insets are applied. - * Otherwise outStableBounds will be empty. Stable bounds should be used - * to compute letterboxed bounds if orientation is not respected when - * insets are applied. + * Stable bounds should be used to compute letterboxed bounds if + * orientation is not respected when insets are applied. + * @param outNonDecorBounds will store the non decor bounds, which are the bounds with non + * decor insets applied, like display cutout and nav bar. */ - private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) { + private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds, + Rect outNonDecorBounds) { outStableBounds.setEmpty(); if (mDisplayContent == null) { return true; } - if (mWmService.mFlags.mInsetsDecoupledConfiguration - && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) { + if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds) { // No insets should be considered any more. return true; } @@ -8973,8 +8981,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ? getFixedRotationTransformDisplayInfo() : mDisplayContent.getDisplayInfo(); final Task task = getTask(); - task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */, - outStableBounds /* outStableBounds */, parentBounds /* bounds */, di); + task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */, + outStableBounds /* outStableBounds */, parentBounds /* bounds */, di, + mResolveConfigHint.mUseLegacyInsetsForStableBounds); final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width() ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; // If orientation does not match the orientation with insets applied, then a @@ -8984,9 +8993,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // have the desired orientation. final boolean orientationRespectedWithInsets = orientation == orientationWithInsets || orientationWithInsets == requestedOrientation; - if (orientationRespectedWithInsets) { - outStableBounds.setEmpty(); - } return orientationRespectedWithInsets; } @@ -9012,9 +9018,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) { final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); final Rect stableBounds = new Rect(); + final Rect outNonDecorBounds = mTmpBounds; // If orientation is respected when insets are applied, then stableBounds will be empty. boolean orientationRespectedWithInsets = - orientationRespectedWithInsets(parentBounds, stableBounds); + orientationRespectedWithInsets(parentBounds, stableBounds, outNonDecorBounds); if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant( getOverrideOrientation())) { // No need to letterbox because of fixed orientation. Display will handle @@ -9031,7 +9038,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect resolvedBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds(); - final int parentOrientation = newParentConfig.orientation; + final int stableBoundsOrientation = stableBounds.width() > stableBounds.height() + ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; + final int parentOrientation = mResolveConfigHint.mUseLegacyInsetsForStableBounds + ? stableBoundsOrientation : newParentConfig.orientation; // If the activity requires a different orientation (either by override or activityInfo), // make it fit the available bounds by scaling down its bounds. @@ -9054,10 +9064,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } + final Rect parentAppBounds = mResolveConfigHint.mUseLegacyInsetsForStableBounds + ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds(); // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app // bounds or stable bounds to unify aspect ratio logic. final Rect parentBoundsWithInsets = orientationRespectedWithInsets - ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds; + ? parentAppBounds : stableBounds; final Rect containingBounds = new Rect(); final Rect containingBoundsWithInsets = new Rect(); // Need to shrink the containing bounds into a square because the parent orientation @@ -9134,8 +9146,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. - getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), - newParentConfig, compatDisplayInsets); + mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets; + computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } @@ -9176,8 +9188,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) { // Compute the configuration based on the resolved bounds. If aspect ratio doesn't // restrict, the bounds should be the requested override bounds. - getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, - getFixedRotationTransformDisplayInfo()); + mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo(); + computeConfigByResolveHint(resolvedConfig, newParentConfiguration); } } @@ -9241,8 +9253,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Use resolvedBounds to compute other override configurations such as appBounds. The bounds // are calculated in compat container space. The actual position on screen will be applied // later, so the calculation is simpler that doesn't need to involve offset from parent. - getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, - compatDisplayInsets); + mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets; + computeConfigByResolveHint(resolvedConfig, newParentConfiguration); // Use current screen layout as source because the size of app is independent to parent. resolvedConfig.screenLayout = computeScreenLayout( getConfiguration().screenLayout, resolvedConfig.screenWidthDp, @@ -10759,7 +10771,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Constructs the environment to simulate the bounds behavior of the given container. */ CompatDisplayInsets(DisplayContent display, ActivityRecord container, - @Nullable Rect fixedOrientationBounds) { + @Nullable Rect fixedOrientationBounds, boolean useOverrideInsets) { mOriginalRotation = display.getRotation(); mIsFloating = container.getWindowConfiguration().tasksAreFloating(); mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation(); @@ -10811,8 +10823,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight; final DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(rotation, dw, dh); - mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets); - mStableInsets[rotation].set(decorInfo.mConfigInsets); + if (useOverrideInsets) { + mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets); + mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets); + } else { + mStableInsets[rotation].set(decorInfo.mConfigInsets); + mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets); + } if (unfilledContainerBounds == null) { continue; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d984fb14f523..a739e577bca3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -886,6 +886,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mRecentTasks.onSystemReadyLocked(); mTaskSupervisor.onSystemReady(); mActivityClientController.onSystemReady(); + mAppWarnings.onSystemReady(); // TODO(b/258792202) Cleanup once ASM is ready to launch ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor()); mGrammaticalManagerInternal = LocalServices.getService( @@ -3782,25 +3783,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r), r.shortComponentName, Boolean.toString(isAutoEnter)); - - // Ensure the ClientTransactionItems are bundled for this operation. - deferWindowLayout(); - try { - r.setPictureInPictureParams(params); - r.mAutoEnteringPip = isAutoEnter; - mRootWindowContainer.moveActivityToPinnedRootTask(r, - null /* launchIntoPipHostActivity */, "enterPictureInPictureMode", - transition); - // Continue the pausing process after entering pip. - if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) { - r.getTask().schedulePauseActivity(r, false /* userLeaving */, - false /* pauseImmediately */, true /* autoEnteringPip */, - "auto-pip"); - } - r.mAutoEnteringPip = false; - } finally { - continueWindowLayout(); - } + r.setPictureInPictureParams(params); + r.mAutoEnteringPip = isAutoEnter; + mRootWindowContainer.moveActivityToPinnedRootTask(r, + null /* launchIntoPipHostActivity */, "enterPictureInPictureMode", + transition); + // Continue the pausing process after entering pip. + if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) { + r.getTask().schedulePauseActivity(r, false /* userLeaving */, + false /* pauseImmediately */, true /* autoEnteringPip */, + "auto-pip"); + } + r.mAutoEnteringPip = false; } }; @@ -6360,7 +6354,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void onPackageDataCleared(String name, int userId) { synchronized (mGlobalLock) { mCompatModePackages.handlePackageDataClearedLocked(name); - mAppWarnings.onPackageDataCleared(name); + mAppWarnings.onPackageDataCleared(name, userId); mPackageConfigPersister.onPackageDataCleared(name, userId); } } @@ -6368,7 +6362,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void onPackageUninstalled(String name, int userId) { synchronized (mGlobalLock) { - mAppWarnings.onPackageUninstalled(name); + mAppWarnings.onPackageUninstalled(name, userId); mCompatModePackages.handlePackageUninstalledLocked(name); mPackageConfigPersister.onPackageUninstall(name, userId); } diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index ad5f4427fbc4..9fd543fca30e 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -16,8 +16,14 @@ package com.android.server.wm; +import static android.os.UserHandle.USER_NULL; +import static android.os.UserHandle.USER_SYSTEM; +import static android.os.UserManager.isHeadlessSystemUserMode; +import static android.os.UserManager.isVisibleBackgroundUsersEnabled; + import android.annotation.NonNull; import android.annotation.UiThread; +import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -26,17 +32,21 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemProperties; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.DisplayMetrics; +import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -44,6 +54,8 @@ import com.android.internal.util.ArrayUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.IoThread; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -65,19 +77,30 @@ class AppWarnings { public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04; public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08; + /** + * Map of package flags for each user. + * Key: {@literal Pair<userId, packageName>} + * Value: Flags + */ @GuardedBy("mPackageFlags") - private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>(); + private final ArrayMap<Pair<Integer, String>, Integer> mPackageFlags = new ArrayMap<>(); private final ActivityTaskManagerService mAtm; - private final Context mUiContext; private final WriteConfigTask mWriteConfigTask; private final UiHandler mUiHandler; private final AtomicFile mConfigFile; - private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog; - private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog; - private DeprecatedTargetSdkVersionDialog mDeprecatedTargetSdkVersionDialog; - private DeprecatedAbiDialog mDeprecatedAbiDialog; + private UserManagerInternal mUserManagerInternal; + + /** + * Maps of app warning dialogs for each user. + * Key: userId + * Value: The warning dialog for specific user + */ + private SparseArray<UnsupportedDisplaySizeDialog> mUnsupportedDisplaySizeDialogs; + private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs; + private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs; + private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs; /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities = @@ -92,12 +115,35 @@ class AppWarnings { public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, Handler uiHandler, File systemDir) { mAtm = atm; - mUiContext = uiContext; mWriteConfigTask = new WriteConfigTask(); mUiHandler = new UiHandler(uiHandler.getLooper()); mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config"); + } + /** + * Called when ActivityManagerService receives its systemReady call during boot. + */ + void onSystemReady() { + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); readConfigFromFileAmsThread(); + + if (!isVisibleBackgroundUsersEnabled()) { + return; + } + + mUserManagerInternal.addUserLifecycleListener( + new UserManagerInternal.UserLifecycleListener() { + @Override + public void onUserRemoved(UserInfo user) { + // Ignore profile user. + if (!user.isFull()) { + return; + } + // Dismiss all warnings and clear all package flags for the user. + mUiHandler.hideDialogsForPackage(/* name= */ null, user.id); + clearAllPackageFlagsForUser(user.id); + } + }); } /** @@ -227,18 +273,20 @@ class AppWarnings { * Called by ActivityManagerService when package data has been cleared. * * @param name the package whose data has been cleared + * @param userId the user where the package resides. */ - public void onPackageDataCleared(String name) { - removePackageAndHideDialogs(name); + public void onPackageDataCleared(String name, int userId) { + removePackageAndHideDialogs(name, userId); } /** * Called by ActivityManagerService when a package has been uninstalled. * * @param name the package that has been uninstalled + * @param userId the user where the package resides. */ - public void onPackageUninstalled(String name) { - removePackageAndHideDialogs(name); + public void onPackageUninstalled(String name, int userId) { + removePackageAndHideDialogs(name, userId); } /** @@ -251,11 +299,24 @@ class AppWarnings { /** * Does what it says on the tin. */ - private void removePackageAndHideDialogs(String name) { - mUiHandler.hideDialogsForPackage(name); + private void removePackageAndHideDialogs(String name, int userId) { + // Per-user AppWarnings only affects the behavior of the devices that enable the visible + // background users. + // To preserve existing behavior of the other devices, handle AppWarnings as a system user + // regardless of the actual user. + if (!isVisibleBackgroundUsersEnabled()) { + userId = USER_SYSTEM; + } else { + // If the userId is of a profile, use the parent user ID, + // since the warning dialogs and the flags for a package are handled per profile group. + userId = mUserManagerInternal.getProfileParentId(userId); + } + + mUiHandler.hideDialogsForPackage(name, userId); synchronized (mPackageFlags) { - if (mPackageFlags.remove(name) != null) { + final Pair<Integer, String> packageKey = Pair.create(userId, name); + if (mPackageFlags.remove(packageKey) != null) { mWriteConfigTask.schedule(); } } @@ -268,10 +329,14 @@ class AppWarnings { */ @UiThread private void hideUnsupportedDisplaySizeDialogUiThread() { - if (mUnsupportedDisplaySizeDialog != null) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; + if (mUnsupportedDisplaySizeDialogs == null) { + return; } + + for (int i = 0; i < mUnsupportedDisplaySizeDialogs.size(); i++) { + mUnsupportedDisplaySizeDialogs.valueAt(i).dismiss(); + } + mUnsupportedDisplaySizeDialogs.clear(); } /** @@ -282,16 +347,24 @@ class AppWarnings { * @param ar record for the activity that triggered the warning */ @UiThread - private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) { - if (mUnsupportedDisplaySizeDialog != null) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; + private void showUnsupportedDisplaySizeDialogUiThread(@NonNull ActivityRecord ar) { + final int userId = getUserIdForActivity(ar); + UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog; + if (mUnsupportedDisplaySizeDialogs != null) { + unsupportedDisplaySizeDialog = mUnsupportedDisplaySizeDialogs.get(userId); + if (unsupportedDisplaySizeDialog != null) { + unsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialogs.remove(userId); + } } - if (ar != null && !hasPackageFlag( - ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) { - mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( - AppWarnings.this, mUiContext, ar.info.applicationInfo); - mUnsupportedDisplaySizeDialog.show(); + if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) { + unsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( + AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); + unsupportedDisplaySizeDialog.show(); + if (mUnsupportedDisplaySizeDialogs == null) { + mUnsupportedDisplaySizeDialogs = new SparseArray<>(); + } + mUnsupportedDisplaySizeDialogs.put(userId, unsupportedDisplaySizeDialog); } } @@ -303,16 +376,24 @@ class AppWarnings { * @param ar record for the activity that triggered the warning */ @UiThread - private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) { - if (mUnsupportedCompileSdkDialog != null) { - mUnsupportedCompileSdkDialog.dismiss(); - mUnsupportedCompileSdkDialog = null; + private void showUnsupportedCompileSdkDialogUiThread(@NonNull ActivityRecord ar) { + final int userId = getUserIdForActivity(ar); + UnsupportedCompileSdkDialog unsupportedCompileSdkDialog; + if (mUnsupportedCompileSdkDialogs != null) { + unsupportedCompileSdkDialog = mUnsupportedCompileSdkDialogs.get(userId); + if (unsupportedCompileSdkDialog != null) { + unsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialogs.remove(userId); + } } - if (ar != null && !hasPackageFlag( - ar.packageName, FLAG_HIDE_COMPILE_SDK)) { - mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog( - AppWarnings.this, mUiContext, ar.info.applicationInfo); - mUnsupportedCompileSdkDialog.show(); + if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_COMPILE_SDK)) { + unsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog( + AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); + unsupportedCompileSdkDialog.show(); + if (mUnsupportedCompileSdkDialogs == null) { + mUnsupportedCompileSdkDialogs = new SparseArray<>(); + } + mUnsupportedCompileSdkDialogs.put(userId, unsupportedCompileSdkDialog); } } @@ -324,16 +405,24 @@ class AppWarnings { * @param ar record for the activity that triggered the warning */ @UiThread - private void showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar) { - if (mDeprecatedTargetSdkVersionDialog != null) { - mDeprecatedTargetSdkVersionDialog.dismiss(); - mDeprecatedTargetSdkVersionDialog = null; + private void showDeprecatedTargetSdkDialogUiThread(@NonNull ActivityRecord ar) { + final int userId = getUserIdForActivity(ar); + DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog; + if (mDeprecatedTargetSdkVersionDialogs != null) { + deprecatedTargetSdkVersionDialog = mDeprecatedTargetSdkVersionDialogs.get(userId); + if (deprecatedTargetSdkVersionDialog != null) { + deprecatedTargetSdkVersionDialog.dismiss(); + mDeprecatedTargetSdkVersionDialogs.remove(userId); + } } - if (ar != null && !hasPackageFlag( - ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) { - mDeprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog( - AppWarnings.this, mUiContext, ar.info.applicationInfo); - mDeprecatedTargetSdkVersionDialog.show(); + if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) { + deprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog( + AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); + deprecatedTargetSdkVersionDialog.show(); + if (mDeprecatedTargetSdkVersionDialogs == null) { + mDeprecatedTargetSdkVersionDialogs = new SparseArray<>(); + } + mDeprecatedTargetSdkVersionDialogs.put(userId, deprecatedTargetSdkVersionDialog); } } @@ -345,16 +434,24 @@ class AppWarnings { * @param ar record for the activity that triggered the warning */ @UiThread - private void showDeprecatedAbiDialogUiThread(ActivityRecord ar) { - if (mDeprecatedAbiDialog != null) { - mDeprecatedAbiDialog.dismiss(); - mDeprecatedAbiDialog = null; + private void showDeprecatedAbiDialogUiThread(@NonNull ActivityRecord ar) { + final int userId = getUserIdForActivity(ar); + DeprecatedAbiDialog deprecatedAbiDialog; + if (mDeprecatedAbiDialogs != null) { + deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId); + if (deprecatedAbiDialog != null) { + deprecatedAbiDialog.dismiss(); + mDeprecatedAbiDialogs.remove(userId); + } } - if (ar != null && !hasPackageFlag( - ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) { - mDeprecatedAbiDialog = new DeprecatedAbiDialog( - AppWarnings.this, mUiContext, ar.info.applicationInfo); - mDeprecatedAbiDialog.show(); + if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) { + deprecatedAbiDialog = new DeprecatedAbiDialog( + AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId); + deprecatedAbiDialog.show(); + if (mDeprecatedAbiDialogs == null) { + mDeprecatedAbiDialogs = new SparseArray<>(); + } + mDeprecatedAbiDialogs.put(userId, deprecatedAbiDialog); } } @@ -365,65 +462,84 @@ class AppWarnings { * * @param name the package for which warnings should be dismissed, or {@code null} to dismiss * all warnings + * @param userId the user where the package resides. */ @UiThread - private void hideDialogsForPackageUiThread(String name) { + private void hideDialogsForPackageUiThread(String name, int userId) { // Hides the "unsupported display" dialog if necessary. - if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals( - mUnsupportedDisplaySizeDialog.mPackageName))) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; + if (mUnsupportedDisplaySizeDialogs != null) { + UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog = + mUnsupportedDisplaySizeDialogs.get(userId); + if (unsupportedDisplaySizeDialog != null && (name == null || name.equals( + unsupportedDisplaySizeDialog.mPackageName))) { + unsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialogs.remove(userId); + } } // Hides the "unsupported compile SDK" dialog if necessary. - if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals( - mUnsupportedCompileSdkDialog.mPackageName))) { - mUnsupportedCompileSdkDialog.dismiss(); - mUnsupportedCompileSdkDialog = null; + if (mUnsupportedCompileSdkDialogs != null) { + UnsupportedCompileSdkDialog unsupportedCompileSdkDialog = + mUnsupportedCompileSdkDialogs.get(userId); + if (unsupportedCompileSdkDialog != null && (name == null || name.equals( + unsupportedCompileSdkDialog.mPackageName))) { + unsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialogs.remove(userId); + } } // Hides the "deprecated target sdk version" dialog if necessary. - if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals( - mDeprecatedTargetSdkVersionDialog.mPackageName))) { - mDeprecatedTargetSdkVersionDialog.dismiss(); - mDeprecatedTargetSdkVersionDialog = null; + if (mDeprecatedTargetSdkVersionDialogs != null) { + DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog = + mDeprecatedTargetSdkVersionDialogs.get(userId); + if (deprecatedTargetSdkVersionDialog != null && (name == null || name.equals( + deprecatedTargetSdkVersionDialog.mPackageName))) { + deprecatedTargetSdkVersionDialog.dismiss(); + mDeprecatedTargetSdkVersionDialogs.remove(userId); + } } // Hides the "deprecated abi" dialog if necessary. - if (mDeprecatedAbiDialog != null && (name == null || name.equals( - mDeprecatedAbiDialog.mPackageName))) { - mDeprecatedAbiDialog.dismiss(); - mDeprecatedAbiDialog = null; + if (mDeprecatedAbiDialogs != null) { + DeprecatedAbiDialog deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId); + if (deprecatedAbiDialog != null && (name == null || name.equals( + deprecatedAbiDialog.mPackageName))) { + deprecatedAbiDialog.dismiss(); + mDeprecatedAbiDialogs.remove(userId); + } } } /** * Returns the value of the flag for the given package. * + * @param userId the user where the package resides. * @param name the package from which to retrieve the flag * @param flag the bitmask for the flag to retrieve * @return {@code true} if the flag is enabled, {@code false} otherwise */ - boolean hasPackageFlag(String name, int flag) { - return (getPackageFlags(name) & flag) == flag; + boolean hasPackageFlag(int userId, String name, int flag) { + return (getPackageFlags(userId, name) & flag) == flag; } /** * Sets the flag for the given package to the specified value. * + * @param userId the user where the package resides. * @param name the package on which to set the flag * @param flag the bitmask for flag to set * @param enabled the value to set for the flag */ - void setPackageFlag(String name, int flag, boolean enabled) { + void setPackageFlag(int userId, String name, int flag, boolean enabled) { synchronized (mPackageFlags) { - final int curFlags = getPackageFlags(name); + final int curFlags = getPackageFlags(userId, name); final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag); if (curFlags != newFlags) { + final Pair<Integer, String> packageKey = Pair.create(userId, name); if (newFlags != 0) { - mPackageFlags.put(name, newFlags); + mPackageFlags.put(packageKey, newFlags); } else { - mPackageFlags.remove(name); + mPackageFlags.remove(packageKey); } mWriteConfigTask.schedule(); } @@ -433,13 +549,95 @@ class AppWarnings { /** * Returns the bitmask of flags set for the specified package. */ - private int getPackageFlags(String name) { + private int getPackageFlags(int userId, String packageName) { synchronized (mPackageFlags) { - return mPackageFlags.getOrDefault(name, 0); + final Pair<Integer, String> packageKey = Pair.create(userId, packageName); + return mPackageFlags.getOrDefault(packageKey, 0); } } /** + * Clear all the package flags for given user. + */ + private void clearAllPackageFlagsForUser(int userId) { + synchronized (mPackageFlags) { + boolean hasPackageFlagsForUser = false; + for (int i = mPackageFlags.size() - 1; i >= 0; i--) { + Pair<Integer, String> key = mPackageFlags.keyAt(i); + if (key.first == userId) { + hasPackageFlagsForUser = true; + mPackageFlags.remove(key); + } + } + + if (hasPackageFlagsForUser) { + mWriteConfigTask.schedule(); + } + } + } + + /** + * Returns the user ID for handling AppWarnings per user. + * Per-user AppWarnings only affects the behavior of the devices that enable + * the visible background users. + * If the device doesn't enable visible background users, it will return the system user ID + * for handling AppWarnings as a system user regardless of the actual user + * to preserve existing behavior of the device. + * Otherwise, it will return the main user (i.e., not a profile) that is assigned to the display + * where the activity is launched. + */ + private @UserIdInt int getUserIdForActivity(@NonNull ActivityRecord ar) { + if (!isVisibleBackgroundUsersEnabled()) { + return USER_SYSTEM; + } + + if (ar.mUserId == USER_SYSTEM) { + return getUserAssignedToDisplay(ar.mDisplayContent.getDisplayId()); + } + + return mUserManagerInternal.getProfileParentId(ar.mUserId); + } + + /** + * Returns the UI context for handling AppWarnings per user. + * Per-user AppWarnings only affects the behavior of the devices that enable + * the visible background users. + * If the device enables the visible background users, it will return the UI context associated + * with the assigned user and the display where the activity is launched. + * If the HSUM device doesn't enable the visible background users, it will return the UI context + * associated with the current user and the default display. + * Otherwise, it will return the UI context associated with the system user and the default + * display. + */ + private Context getUiContextForActivity(@NonNull ActivityRecord ar) { + if (!isVisibleBackgroundUsersEnabled()) { + if (!isHeadlessSystemUserMode()) { + return mAtm.getUiContext(); + } + + Context uiContextForCurrentUser = mAtm.getUiContext().createContextAsUser( + new UserHandle(mAtm.getCurrentUserId()), /* flags= */ 0); + return uiContextForCurrentUser; + } + + DisplayContent dc = ar.mDisplayContent; + Context systemUiContext = dc.getDisplayPolicy().getSystemUiContext(); + int assignedUser = getUserAssignedToDisplay(dc.getDisplayId()); + Context uiContextForUser = systemUiContext.createContextAsUser( + new UserHandle(assignedUser), /* flags= */ 0); + return uiContextForUser; + } + + /** + * Returns the main user that is assigned to the display. + * + * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}. + */ + private @UserIdInt int getUserAssignedToDisplay(int displayId) { + return mUserManagerInternal.getUserAssignedToDisplay(displayId); + } + + /** * Handles messages on the system process UI thread. */ private final class UiHandler extends Handler { @@ -470,7 +668,8 @@ class AppWarnings { } break; case MSG_HIDE_DIALOGS_FOR_PACKAGE: { final String name = (String) msg.obj; - hideDialogsForPackageUiThread(name); + final int userId = (int) msg.arg1; + hideDialogsForPackageUiThread(name, userId); } break; case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: { final ActivityRecord ar = (ActivityRecord) msg.obj; @@ -508,20 +707,24 @@ class AppWarnings { obtainMessage(MSG_SHOW_DEPRECATED_ABI_DIALOG, r).sendToTarget(); } - public void hideDialogsForPackage(String name) { - obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget(); + public void hideDialogsForPackage(String name, int userId) { + obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget(); } } static class BaseDialog { final AppWarnings mManager; + final Context mUiContext; final String mPackageName; + final int mUserId; AlertDialog mDialog; private BroadcastReceiver mCloseReceiver; - BaseDialog(AppWarnings manager, String packageName) { + BaseDialog(AppWarnings manager, Context uiContext, String packageName, int userId) { mManager = manager; + mUiContext = uiContext; mPackageName = packageName; + mUserId = userId; } @UiThread @@ -532,11 +735,11 @@ class AppWarnings { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - mManager.mUiHandler.hideDialogsForPackage(mPackageName); + mManager.mUiHandler.hideDialogsForPackage(mPackageName, mUserId); } } }; - mManager.mUiContext.registerReceiver(mCloseReceiver, + mUiContext.registerReceiver(mCloseReceiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), Context.RECEIVER_EXPORTED); } @@ -548,7 +751,7 @@ class AppWarnings { void dismiss() { if (mDialog == null) return; if (mCloseReceiver != null) { - mManager.mUiContext.unregisterReceiver(mCloseReceiver); + mUiContext.unregisterReceiver(mCloseReceiver); mCloseReceiver = null; } mDialog.dismiss(); @@ -558,12 +761,13 @@ class AppWarnings { private final class WriteConfigTask implements Runnable { private static final long WRITE_CONFIG_DELAY_MS = 10000; - final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags = + final AtomicReference<ArrayMap<Pair<Integer, String>, Integer>> mPendingPackageFlags = new AtomicReference<>(); @Override public void run() { - final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null); + final ArrayMap<Pair<Integer, String>, Integer> packageFlags = + mPendingPackageFlags.getAndSet(null); if (packageFlags != null) { writeConfigToFile(packageFlags); } @@ -579,7 +783,7 @@ class AppWarnings { /** Writes the configuration file. */ @WorkerThread - private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) { + private void writeConfigToFile(@NonNull ArrayMap<Pair<Integer, String>, Integer> packageFlags) { FileOutputStream fos = null; try { fos = mConfigFile.startWrite(); @@ -590,13 +794,16 @@ class AppWarnings { out.startTag(null, "packages"); for (int i = 0; i < packageFlags.size(); i++) { - final String pkg = packageFlags.keyAt(i); + final Pair<Integer, String> key = packageFlags.keyAt(i); + final int userId = key.first; + final String packageName = key.second; final int mode = packageFlags.valueAt(i); if (mode == 0) { continue; } out.startTag(null, "package"); - out.attribute(null, "name", pkg); + out.attributeInt(null, "user", userId); + out.attribute(null, "name", packageName); out.attributeInt(null, "flags", mode); out.endTag(null, "package"); } @@ -616,7 +823,7 @@ class AppWarnings { /** * Reads the configuration file and populates the package flags. * <p> - * <strong>Note:</strong> Must be called from the constructor (and thus on the + * <strong>Note:</strong> Must be called from #onSystemReady() (and thus on the * ActivityManagerService thread) since we don't synchronize on config. */ private void readConfigFromFileAmsThread() { @@ -639,21 +846,58 @@ class AppWarnings { String tagName = parser.getName(); if ("packages".equals(tagName)) { eventType = parser.next(); + boolean writeConfigToFileNeeded = false; do { if (eventType == XmlPullParser.START_TAG) { tagName = parser.getName(); if (parser.getDepth() == 2) { if ("package".equals(tagName)) { + final int userId = parser.getAttributeInt( + null, "user", USER_NULL); final String name = parser.getAttributeValue(null, "name"); if (name != null) { int flagsInt = parser.getAttributeInt(null, "flags", 0); - mPackageFlags.put(name, flagsInt); + if (userId != USER_NULL) { + final Pair<Integer, String> packageKey = + Pair.create(userId, name); + mPackageFlags.put(packageKey, flagsInt); + } else { + // This is for compatibility with existing configuration + // file written from legacy logic(pre-V) which does not have + // the flags per-user. (b/296334639) + writeConfigToFileNeeded = true; + if (!isVisibleBackgroundUsersEnabled()) { + // To preserve existing behavior of the devices that + // doesn't enable visible background users, populate + // the flags for a package as the system user. + final Pair<Integer, String> packageKey = + Pair.create(USER_SYSTEM, name); + mPackageFlags.put(packageKey, flagsInt); + } else { + // To manage the flags per user in the device that + // enable visible background users, populate the flags + // for all existing non-profile human user. + UserInfo[] users = mUserManagerInternal.getUserInfos(); + for (UserInfo userInfo : users) { + if (!userInfo.isFull()) { + continue; + } + final Pair<Integer, String> packageKey = + Pair.create(userInfo.id, name); + mPackageFlags.put(packageKey, flagsInt); + } + } + } } } } } eventType = parser.next(); } while (eventType != XmlPullParser.END_DOCUMENT); + + if (writeConfigToFileNeeded) { + mWriteConfigTask.schedule(); + } } } catch (XmlPullParserException e) { Slog.w(TAG, "Error reading package metadata", e); diff --git a/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java b/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java index e96208d3d0b3..46b34a1f4e16 100644 --- a/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java +++ b/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java @@ -28,8 +28,8 @@ import com.android.internal.R; class DeprecatedAbiDialog extends AppWarnings.BaseDialog { DeprecatedAbiDialog(final AppWarnings manager, Context context, - ApplicationInfo appInfo) { - super(manager, appInfo.packageName); + ApplicationInfo appInfo, int userId) { + super(manager, context, appInfo.packageName, userId); final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm, @@ -41,7 +41,7 @@ class DeprecatedAbiDialog extends AppWarnings.BaseDialog { final AlertDialog.Builder builder = new AlertDialog.Builder(context) .setPositiveButton(R.string.ok, (dialog, which) -> manager.setPackageFlag( - mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_ABI, true)) + mUserId, mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_ABI, true)) .setMessage(message) .setTitle(label); diff --git a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java index 1a7a9b258400..ce4238582b26 100644 --- a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java +++ b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java @@ -31,8 +31,8 @@ import com.android.server.utils.AppInstallerUtil; class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog { DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context, - ApplicationInfo appInfo) { - super(manager, appInfo.packageName); + ApplicationInfo appInfo, int userId) { + super(manager, context, appInfo.packageName, userId); final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm, @@ -44,7 +44,7 @@ class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog { final AlertDialog.Builder builder = new AlertDialog.Builder(context) .setPositiveButton(R.string.ok, (dialog, which) -> manager.setPackageFlag( - mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true)) + mUserId, mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true)) .setMessage(message) .setTitle(label); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 5e0d4f9a33f9..84dadab7da59 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -41,7 +41,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; @@ -986,19 +985,6 @@ public class DisplayPolicy { + " to fit insets. fitInsetsTypes=" + WindowInsets.Type.toString( attrs.getFitInsetsTypes())); } - if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0 - && attrs.layoutInDisplayCutoutMode - != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { - // A non-translucent main window of the app enforced to go edge-to-edge - // isn't allowed to fit display cutout, or it will cause software bezels. - throw new IllegalArgumentException("Illegal attributes: Main window of " - + win.mActivityRecord.getName() + " that isn't translucent and" - + " targets SDK level " + win.mActivityRecord.mTargetSdk - + " (>= 35) trying to specify layoutInDisplayCutoutMode as '" - + WindowManager.LayoutParams.layoutInDisplayCutoutModeToString( - attrs.layoutInDisplayCutoutMode) - + "' instead of 'always'"); - } } break; } @@ -1939,6 +1925,11 @@ public class DisplayPolicy { */ final Rect mOverrideConfigInsets = new Rect(); + /** + * Override value of mNonDecorInsets for app compatibility purpose. + */ + final Rect mOverrideNonDecorInsets = new Rect(); + /** The display frame available after excluding {@link #mNonDecorInsets}. */ final Rect mNonDecorFrame = new Rect(); @@ -1954,6 +1945,11 @@ public class DisplayPolicy { */ final Rect mOverrideConfigFrame = new Rect(); + /** + * Override value of mNonDecorFrame for app compatibility purpose. + */ + final Rect mOverrideNonDecorFrame = new Rect(); + private boolean mNeedUpdate = true; InsetsState update(DisplayContent dc, int rotation, int w, int h) { @@ -1973,17 +1969,26 @@ public class DisplayPolicy { ? configInsets : insetsState.calculateInsets(displayFrame, dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */); + final Insets overrideDecorInsets = dc.mWmService.mDecorTypes + == dc.mWmService.mOverrideDecorTypes + ? decor + : insetsState.calculateInsets(displayFrame, + dc.mWmService.mOverrideDecorTypes, true /* ignoreVisibility */); mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom); mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right, configInsets.bottom); mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top, overrideConfigInsets.right, overrideConfigInsets.bottom); + mOverrideNonDecorInsets.set(overrideDecorInsets.left, overrideDecorInsets.top, + overrideDecorInsets.right, overrideDecorInsets.bottom); mNonDecorFrame.set(displayFrame); mNonDecorFrame.inset(mNonDecorInsets); mConfigFrame.set(displayFrame); mConfigFrame.inset(mConfigInsets); mOverrideConfigFrame.set(displayFrame); mOverrideConfigFrame.inset(mOverrideConfigInsets); + mOverrideNonDecorFrame.set(displayFrame); + mOverrideNonDecorFrame.inset(mOverrideNonDecorInsets); mNeedUpdate = false; return insetsState; } @@ -1992,9 +1997,11 @@ public class DisplayPolicy { mNonDecorInsets.set(other.mNonDecorInsets); mConfigInsets.set(other.mConfigInsets); mOverrideConfigInsets.set(other.mOverrideConfigInsets); + mOverrideNonDecorInsets.set(other.mOverrideNonDecorInsets); mNonDecorFrame.set(other.mNonDecorFrame); mConfigFrame.set(other.mConfigFrame); mOverrideConfigFrame.set(other.mOverrideConfigFrame); + mOverrideNonDecorFrame.set(other.mOverrideNonDecorFrame); mNeedUpdate = false; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 218fb7f6b817..804201088c59 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2188,38 +2188,20 @@ class TaskFragment extends WindowContainer<WindowContainer> { return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID; } - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig) { - computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - null /* compatInsets */); + static class ConfigOverrideHint { + @Nullable DisplayInfo mTmpOverrideDisplayInfo; + @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets; + boolean mUseLegacyInsetsForStableBounds; } void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) { - if (overrideDisplayInfo != null) { - // Make sure the screen related configs can be computed by the provided display info. - inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; - invalidateAppBoundsConfig(inOutConfig); - } - computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo, - null /* compatInsets */); - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { - if (compatInsets != null) { - // Make sure the app bounds can be computed by the compat insets. - invalidateAppBoundsConfig(inOutConfig); - } - computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - compatInsets); + @NonNull Configuration parentConfig) { + computeConfigResourceOverrides(inOutConfig, parentConfig, null /* configOverrideHint */); } /** * Forces the app bounds related configuration can be computed by - * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo, - * ActivityRecord.CompatDisplayInsets)}. + * {@link #computeConfigResourceOverrides(Configuration, Configuration, ConfigOverrideHint)}. */ private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) { final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds(); @@ -2239,8 +2221,24 @@ class TaskFragment extends WindowContainer<WindowContainer> { * just be inherited from the parent configuration. **/ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { + @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) { + DisplayInfo overrideDisplayInfo = null; + ActivityRecord.CompatDisplayInsets compatInsets = null; + boolean useLegacyInsetsForStableBounds = false; + if (overrideHint != null) { + overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo; + compatInsets = overrideHint.mTmpCompatInsets; + useLegacyInsetsForStableBounds = overrideHint.mUseLegacyInsetsForStableBounds; + if (overrideDisplayInfo != null) { + // Make sure the screen related configs can be computed by the provided + // display info. + inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; + } + if (overrideDisplayInfo != null || compatInsets != null) { + // Make sure the app bounds can be computed by the compat insets. + invalidateAppBoundsConfig(inOutConfig); + } + } int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = parentConfig.windowConfiguration.getWindowingMode(); @@ -2309,7 +2307,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { // area, i.e. the screen area without the system bars. // The non decor inset are areas that could never be removed in Honeycomb. See // {@link WindowManagerPolicy#getNonDecorInsetsLw}. - calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di); + calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di, + useLegacyInsetsForStableBounds); } else { // Apply the given non-decor and stable insets to calculate the corresponding bounds // for screen size of configuration. @@ -2407,9 +2406,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { * @param outNonDecorBounds where to place bounds with non-decor insets applied. * @param outStableBounds where to place bounds with stable insets applied. * @param bounds the bounds to inset. + * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame + * for apps targeting U or before when calculating stable bounds. */ void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds, - DisplayInfo displayInfo) { + DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) { outNonDecorBounds.set(bounds); outStableBounds.set(bounds); if (mDisplayContent == null) { @@ -2420,8 +2421,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { final DisplayPolicy policy = mDisplayContent.getDisplayPolicy(); final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo( displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight); - intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets); - intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets); + if (!useLegacyInsetsForStableBounds) { + intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets); + intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets); + } else { + intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets); + intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mOverrideNonDecorInsets); + } } /** diff --git a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java index f376e8b1f9ed..0655068aae86 100644 --- a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java +++ b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java @@ -32,8 +32,8 @@ import com.android.server.utils.AppInstallerUtil; class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog { UnsupportedCompileSdkDialog(final AppWarnings manager, Context context, - ApplicationInfo appInfo) { - super(manager, appInfo.packageName); + ApplicationInfo appInfo, int userId) { + super(manager, context, appInfo.packageName, userId); final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm, @@ -68,6 +68,6 @@ class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog { final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); alwaysShow.setChecked(true); alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( - mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked)); + mUserId, mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked)); } } diff --git a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java index b11c22de4286..5e40d9c3e1b0 100644 --- a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java +++ b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java @@ -30,8 +30,8 @@ import com.android.internal.R; class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog { UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context, - ApplicationInfo appInfo) { - super(manager, appInfo.packageName); + ApplicationInfo appInfo, int userId) { + super(manager, context, appInfo.packageName, userId); final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm, @@ -59,6 +59,6 @@ class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog { final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); alwaysShow.setChecked(true); alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( - mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked)); + mUserId, mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked)); } } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 5c24eee63317..9d1551c12216 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -193,11 +193,11 @@ class WallpaperWindowToken extends WindowToken { if (mVisibleRequested != visible) { // Before setting mVisibleRequested so we can track changes. final WindowState wpTarget = mDisplayContent.mWallpaperController.getWallpaperTarget(); - final boolean isTargetNotCollectedActivity = wpTarget != null - && wpTarget.mActivityRecord != null - && !mTransitionController.isCollecting(wpTarget.mActivityRecord); - // Skip collecting requesting-invisible wallpaper if the wallpaper target is an activity - // and it is not collected. Because the visibility change may be called after the + final boolean isTargetNotCollectedActivity = wpTarget == null + || (wpTarget.mActivityRecord != null + && !mTransitionController.isCollecting(wpTarget.mActivityRecord)); + // Skip collecting requesting-invisible wallpaper if the wallpaper target is empty or + // a non-collected activity. Because the visibility change may be called after the // transition of activity is finished, e.g. WallpaperController#hideWallpapers from // hiding surface of the target. Then if there is a next transition, the wallpaper // change may be collected into the unrelated transition and cause a weird animation. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 143605ac7320..1496ae02427f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -570,6 +570,8 @@ public class WindowManagerService extends IWindowManager.Stub final int mOverrideConfigTypes; + final int mOverrideDecorTypes; + final boolean mLimitedAlphaCompositing; final int mMaxUiWidth; @@ -1255,10 +1257,13 @@ public class WindowManagerService extends IWindowManager.Stub if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) { // If the global new behavior is not there, but the partial decouple flag is on. mOverrideConfigTypes = 0; + mOverrideDecorTypes = 0; } else { mOverrideConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars(); + mOverrideDecorTypes = WindowInsets.Type.displayCutout() + | WindowInsets.Type.navigationBars(); } mLetterboxConfiguration = new LetterboxConfiguration( diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index b999305fbee0..736b05189a72 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -1,12 +1,8 @@ -# Display -per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com - # Input per-file com_android_server_input_* = file:/INPUT_OWNERS # Power -per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com -per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com +per-file com_android_server_HardwarePropertiesManagerService.cpp = file:/services/core/java/com/android/server/power/OWNERS # BatteryStats per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS @@ -16,6 +12,7 @@ per-file com_android_server_SystemClock* = file:/services/core/java/com/android/ per-file com_android_server_Usb* = file:/services/usb/OWNERS per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS per-file com_android_server_accessibility_* = file:/services/accessibility/OWNERS +per-file com_android_server_display_* = file:/services/core/java/com/android/server/display/OWNERS per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS per-file com_android_server_lights_* = file:/services/core/java/com/android/server/lights/OWNERS per-file com_android_server_location_* = file:/location/java/android/location/OWNERS diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp index 8ca5333a65bd..da95666fb7c4 100644 --- a/services/core/jni/com_android_server_utils_AnrTimer.cpp +++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp @@ -487,7 +487,6 @@ class AnrTimerService::Ticker { timer_id_t front = headTimerId(); auto found = running_.find(key); if (found != running_.end()) running_.erase(found); - if (front != headTimerId()) restartLocked(); } // Remove every timer associated with the service. @@ -501,7 +500,6 @@ class AnrTimerService::Ticker { i++; } } - if (front != headTimerId()) restartLocked(); } // Return the number of timers still running. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index cb637579d8db..e1ad979ded96 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -107,6 +107,8 @@ import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOT import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION; import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS; import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION; +import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND; +import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; @@ -886,10 +888,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "enable_permission_based_access"; private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - // TODO(b/266831522) remove the flag after rollout. - private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions"; - private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true; - private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3; /** @@ -3689,26 +3687,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.handleStartUser(userId); } - void pushUserControlDisabledPackagesLocked(int userId) { - final int targetUserId; - final ActiveAdmin owner; - if (getDeviceOwnerUserIdUncheckedLocked() == userId) { - owner = getDeviceOwnerAdminLocked(); - targetUserId = UserHandle.USER_ALL; - } else { - owner = getProfileOwnerAdminLocked(userId); - targetUserId = userId; - } - - List<String> protectedPackages = (owner == null || owner.protectedPackages == null) - ? null : owner.protectedPackages; - mInjector.binderWithCleanCallingIdentity(() -> - mInjector.getPackageManagerInternal().setOwnerProtectedPackages( - targetUserId, protectedPackages)); - mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages), - targetUserId); - } - void handleUnlockUser(int userId) { startOwnerService(userId, "unlock-user"); mDevicePolicyEngine.handleUnlockUser(userId); @@ -15913,14 +15891,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean isApplicationExemptionsFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - APPLICATION_EXEMPTIONS_FLAG, - DEFAULT_APPLICATION_EXEMPTIONS_FLAG); - } - - @Override public List<Bundle> getApplicationRestrictionsPerAdminForUser( String packageName, @UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId @@ -20378,34 +20348,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)); final CallerIdentity caller = getCallerIdentity(callerPackage); - final ApplicationInfo packageInfo; - packageInfo = getPackageInfoWithNullCheck(packageName, caller); + final AppOpsManager appOpsMgr = mInjector.getAppOpsManager(); + final ApplicationInfo appInfo = getPackageInfoWithNullCheck(packageName, caller); + final int uid = appInfo.uid; - for (Map.Entry<Integer, String> entry : - APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) { - int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow( - entry.getValue(), packageInfo.uid, packageInfo.packageName); - int newMode = ArrayUtils.contains(exemptions, entry.getKey()) - ? MODE_ALLOWED : MODE_DEFAULT; - mInjector.binderWithCleanCallingIdentity(() -> { + mInjector.binderWithCleanCallingIdentity(() -> { + APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.forEach((exemption, appOp) -> { + int currentMode = appOpsMgr.unsafeCheckOpNoThrow(appOp, uid, packageName); + int newMode = ArrayUtils.contains(exemptions, exemption) + ? MODE_ALLOWED : MODE_DEFAULT; if (currentMode != newMode) { - mInjector.getAppOpsManager() - .setMode(entry.getValue(), - packageInfo.uid, - packageName, - newMode); + appOpsMgr.setMode(appOp, uid, packageName, newMode); + + // If the user has already disabled background usage for the package, it won't + // have OP_RUN_ANY_IN_BACKGROUND app op and won't execute in the background. The + // code below grants that app op, and once the exemption is in place, the user + // won't be able to disable background usage anymore. + if (Flags.powerExemptionBgUsageFix() + && exemption == EXEMPT_FROM_POWER_RESTRICTIONS + && newMode == MODE_ALLOWED) { + setBgUsageAppOp(appOpsMgr, appInfo); + } } }); - } + }); + String[] appOpExemptions = new String[exemptions.length]; for (int i = 0; i < exemptions.length; i++) { appOpExemptions[i] = APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.get(exemptions[i]); } DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS) - .setAdmin(caller.getPackageName()) - .setStrings(packageName, appOpExemptions) - .write(); + .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS) + .setAdmin(caller.getPackageName()) + .setStrings(packageName, appOpExemptions) + .write(); + } + + static void setBgUsageAppOp(AppOpsManager appOpsMgr, ApplicationInfo appInfo) { + appOpsMgr.setMode(OP_RUN_ANY_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED); + if (appInfo.targetSdkVersion < Build.VERSION_CODES.O) { + appOpsMgr.setMode(OP_RUN_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED); + } } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 42ac998bf96c..d02cfee72aa2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -357,7 +357,8 @@ class OwnersData { @Override boolean shouldWrite() { - return (mDeviceOwner != null) || (mSystemUpdatePolicy != null) + return Flags.alwaysPersistDo() + || (mDeviceOwner != null) || (mSystemUpdatePolicy != null) || (mSystemUpdateInfo != null); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index e713a827fc76..7a9fa0fb5658 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -163,8 +163,7 @@ final class PolicyDefinition<V> { new NoArgsPolicyKey( DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY), new StringSetUnion(), - (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> - PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId), + PolicyEnforcerCallbacks::setUserControlDisabledPackages, new StringSetPolicySerializer()); // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index a7adc5b6d925..a0d9be549880 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; @@ -29,6 +30,7 @@ import android.app.admin.PackagePermissionPolicyKey; import android.app.admin.PackagePolicyKey; import android.app.admin.PolicyKey; import android.app.admin.UserRestrictionPolicyKey; +import android.app.admin.flags.Flags; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Context; @@ -37,6 +39,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -183,15 +186,31 @@ final class PolicyEnforcerCallbacks { } static boolean setUserControlDisabledPackages( - @Nullable Set<String> packages, int userId) { + @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) { Binder.withCleanCallingIdentity(() -> { - LocalServices.getService(PackageManagerInternal.class) - .setOwnerProtectedPackages( - userId, - packages == null ? null : packages.stream().toList()); + PackageManagerInternal pmi = + LocalServices.getService(PackageManagerInternal.class); + pmi.setOwnerProtectedPackages(userId, + packages == null ? null : packages.stream().toList()); LocalServices.getService(UsageStatsManagerInternal.class) - .setAdminProtectedPackages( + .setAdminProtectedPackages( packages == null ? null : new ArraySet<>(packages), userId); + + if (Flags.disallowUserControlBgUsageFix()) { + if (packages == null) { + return; + } + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + for (var pkg : packages) { + final var appInfo = pmi.getApplicationInfo(pkg, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + Process.myUid(), userId); + if (appInfo != null) { + DevicePolicyManagerService.setBgUsageAppOp(appOpsManager, appInfo); + } + } + } }); return true; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java index fe7bbe0ecf4b..ea08be4f1be4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java @@ -157,7 +157,6 @@ public class ExternalDisplayPolicyTest { verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean()); verify(mMockedDisplayNotificationManager, times(2)) .onHighTemperatureExternalDisplayNotAllowed(); - verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt()); } @Test @@ -168,7 +167,6 @@ public class ExternalDisplayPolicyTest { verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean()); verify(mMockedDisplayNotificationManager, never()) .onHighTemperatureExternalDisplayNotAllowed(); - verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt()); } @Test @@ -186,7 +184,6 @@ public class ExternalDisplayPolicyTest { // Expected only 1 invocation, upon critical temperature. verify(mMockedDisplayNotificationManager).onHighTemperatureExternalDisplayNotAllowed(); verify(mMockedExternalDisplayStatsService).onDisplayDisabled(eq(EXTERNAL_DISPLAY_ID)); - verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt()); } @Test @@ -194,7 +191,6 @@ public class ExternalDisplayPolicyTest { mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay, /*enabled=*/ true); assertDisplaySetEnabled(/*enabled=*/ true); - verify(mMockedInjector).onExternalDisplayReadyToBeEnabled(eq(EXTERNAL_DISPLAY_ID)); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 040ae96d902e..4591d91ceea7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -1952,7 +1952,7 @@ public class DisplayModeDirectorTest { SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID_2, votes); - director.getDisplayObserver().onExternalDisplayReadyToBeEnabled(DISPLAY_ID_2); + director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2); director.injectVotesByDisplay(votesByDisplay); var desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID_2); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index 7cfc1dd8db65..2d317af3d85d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; import android.content.res.Resources; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.Looper; @@ -108,7 +109,7 @@ public class DisplayObserverTest { private Context mContext; private DisplayModeDirector.Injector mInjector; private Handler mHandler; - private DisplayModeDirector.DisplayObserver mObserver; + private DisplayManager.DisplayListener mObserver; private Resources mResources; @Mock private DisplayManagerFlags mDisplayManagerFlags; @@ -162,7 +163,6 @@ public class DisplayObserverTest { .isEqualTo(null); // Testing that the vote is not added when display is added because feature is disabled - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); @@ -196,7 +196,6 @@ public class DisplayObserverTest { init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); @@ -248,7 +247,6 @@ public class DisplayObserverTest { init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); @@ -281,7 +279,6 @@ public class DisplayObserverTest { init(); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY); mObserver.onDisplayAdded(DEFAULT_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(expectedResolutionVote); @@ -303,7 +300,6 @@ public class DisplayObserverTest { .thenReturn(MAX_HEIGHT); init(); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY); mObserver.onDisplayAdded(DEFAULT_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); @@ -323,7 +319,6 @@ public class DisplayObserverTest { .thenReturn(MAX_HEIGHT); init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo( @@ -343,7 +338,6 @@ public class DisplayObserverTest { when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); @@ -366,7 +360,6 @@ public class DisplayObserverTest { .thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo( Vote.forPhysicalRefreshRates( @@ -390,7 +383,6 @@ public class DisplayObserverTest { .thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); } @@ -405,7 +397,6 @@ public class DisplayObserverTest { when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); - mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 4c7a8fef6f0b..959078326f6b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -205,8 +205,6 @@ public class MockingOomAdjusterTests { new ProcessStatsService(sService, new File(sContext.getFilesDir(), "procstats"))); setFieldValue(ActivityManagerService.class, sService, "mBackupTargets", mock(SparseArray.class)); - setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler", - mock(OomAdjProfiler.class)); setFieldValue(ActivityManagerService.class, sService, "mUserController", mock(UserController.class)); setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler); diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp index f38723854d6b..12a70387affd 100644 --- a/services/tests/selinux/Android.bp +++ b/services/tests/selinux/Android.bp @@ -52,6 +52,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.ext.truth", "androidx.test.runner", + "compatibility-device-util-axt", "services.core", ], test_suites: [ diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java index b36c9bdaf456..e86108d84538 100644 --- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java +++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java @@ -15,98 +15,144 @@ */ package com.android.server.selinux; -import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER; -import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER; -import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories; import static com.google.common.truth.Truth.assertThat; +import android.provider.DeviceConfig; + import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.regex.Matcher; + @RunWith(AndroidJUnit4.class) public class SelinuxAuditLogsBuilderTest { - private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder(); + private static final String TEST_DOMAIN = "test_domain"; + + private SelinuxAuditLogBuilder mAuditLogBuilder; + private Matcher mScontextMatcher; + private Matcher mTcontextMatcher; + private Matcher mPathMatcher; + + @Before + public void setUp() { + runWithShellPermissionIdentity( + () -> + DeviceConfig.setLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN, + TEST_DOMAIN)); + + mAuditLogBuilder = new SelinuxAuditLogBuilder(); + mScontextMatcher = mAuditLogBuilder.mScontextMatcher; + mTcontextMatcher = mAuditLogBuilder.mTcontextMatcher; + mPathMatcher = mAuditLogBuilder.mPathMatcher; + } + + @After + public void tearDown() { + runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides()); + } @Test public void testMatcher_scontext() { - assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue(); - assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit"); - assertThat(SCONTEXT_MATCHER.group("scategories")).isNull(); + assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isTrue(); + assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN); + assertThat(mScontextMatcher.group("scategories")).isNull(); - assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue(); - assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit"); - assertThat(toCategories(SCONTEXT_MATCHER.group("scategories"))) + assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches()) + .isTrue(); + assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN); + assertThat(toCategories(mScontextMatcher.group("scategories"))) .isEqualTo(new int[] {123, 456}); - assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse(); - assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse(); - assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse(); + assertThat(mScontextMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse(); + assertThat(mScontextMatcher.reset("u:object_r:" + TEST_DOMAIN + ":s0").matches()).isFalse(); + assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:p123").matches()).isFalse(); } @Test public void testMatcher_tcontext() { - assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue(); - assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type"); - assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull(); + assertThat(mTcontextMatcher.reset("u:object_r:target_type:s0").matches()).isTrue(); + assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type"); + assertThat(mTcontextMatcher.group("tcategories")).isNull(); - assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue(); - assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2"); - assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666}); + assertThat(mTcontextMatcher.reset("u:object_r:target_type2:s0:c666").matches()).isTrue(); + assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type2"); + assertThat(toCategories(mTcontextMatcher.group("tcategories"))).isEqualTo(new int[] {666}); - assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse(); - assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse(); + assertThat(mTcontextMatcher.reset("u:r:target_type:s0").matches()).isFalse(); + assertThat(mTcontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:x456").matches()).isFalse(); } @Test public void testMatcher_path() { - assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue(); - assertThat(PATH_MATCHER.group("path")).isEqualTo("/data"); - assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue(); - assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local"); - assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue(); - assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local"); - - assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse(); - assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse(); + assertThat(mPathMatcher.reset("\"/data\"").matches()).isTrue(); + assertThat(mPathMatcher.group("path")).isEqualTo("/data"); + assertThat(mPathMatcher.reset("\"/data/local\"").matches()).isTrue(); + assertThat(mPathMatcher.group("path")).isEqualTo("/data/local"); + assertThat(mPathMatcher.reset("\"/data/local/tmp\"").matches()).isTrue(); + assertThat(mPathMatcher.group("path")).isEqualTo("/data/local"); + + assertThat(mPathMatcher.reset("\"/data/local").matches()).isFalse(); + assertThat(mPathMatcher.reset("\"_data_local\"").matches()).isFalse(); + } + + @Test + public void testMatcher_scontextDefaultConfig() { + runWithShellPermissionIdentity( + () -> + DeviceConfig.clearLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN)); + + Matcher scontexMatcher = new SelinuxAuditLogBuilder().mScontextMatcher; + + assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isFalse(); + assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches()) + .isFalse(); + assertThat(scontexMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse(); } @Test public void testSelinuxAuditLogsBuilder_noOptionals() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 tcontext=u:object_r:t:s0" + " tclass=c"); - assertAuditLog( - mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c"); + assertAuditLog(mAuditLogBuilder.build(), true, new String[] {"p"}, TEST_DOMAIN, "t", "c"); mAuditLogBuilder.reset( "tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0" - + " scontext=u:r:sdk_sandbox_audit:s0"); + + " scontext=u:r:" + + TEST_DOMAIN + + ":s0"); assertAuditLog( - mAuditLogBuilder.build(), - true, - new String[] {"p2"}, - "sdk_sandbox_audit", - "t2", - "c2"); + mAuditLogBuilder.build(), true, new String[] {"p2"}, TEST_DOMAIN, "t2", "c2"); } @Test public void testSelinuxAuditLogsBuilder_withCategories() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0:c123" + " tcontext=u:object_r:t:s0:c456,c666 tclass=c"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "t", new int[] {456, 666}, @@ -118,13 +164,15 @@ public class SelinuxAuditLogsBuilderTest { @Test public void testSelinuxAuditLogsBuilder_withPath() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\"" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 path=\"/very/long/path\"" + " tcontext=u:object_r:t:s0 tclass=c"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "t", null, @@ -136,13 +184,15 @@ public class SelinuxAuditLogsBuilderTest { @Test public void testSelinuxAuditLogsBuilder_withPermissive() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 permissive=0" + " tcontext=u:object_r:t:s0 tclass=c"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "t", null, @@ -151,13 +201,15 @@ public class SelinuxAuditLogsBuilderTest { false); mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 tcontext=u:object_r:t:s0 tclass=c" + " permissive=1"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "t", null, @@ -166,6 +218,40 @@ public class SelinuxAuditLogsBuilderTest { true); } + @Test + public void testSelinuxAuditLogsBuilder_wrongConfig() { + String notARegexDomain = "not]a[regex"; + runWithShellPermissionIdentity( + () -> + DeviceConfig.setLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN, + notARegexDomain)); + SelinuxAuditLogBuilder noOpBuilder = new SelinuxAuditLogBuilder(); + + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 tcontext=u:object_r:t:s0 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0:c123 tcontext=u:object_r:t:s0:c456,c666 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 path=\"/very/long/path\"" + + " tcontext=u:object_r:t:s0 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 permissive=0 tcontext=u:object_r:t:s0 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + } + private void assertAuditLog( SelinuxAuditLog auditLog, boolean granted, diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java index 4a70ad38a76f..b6ccf5e0ad80 100644 --- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java +++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java @@ -15,6 +15,7 @@ */ package com.android.server.selinux; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -27,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import android.provider.DeviceConfig; import android.util.EventLog; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -50,6 +52,7 @@ public class SelinuxAuditLogsCollectorTest { // Fake tag to use for testing private static final int ANSWER_TAG = 42; + private static final String TEST_DOMAIN = "test_domain"; private final MockClock mClock = new MockClock(); @@ -64,6 +67,14 @@ public class SelinuxAuditLogsCollectorTest { @Before public void setUp() { + runWithShellPermissionIdentity( + () -> + DeviceConfig.setLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN, + TEST_DOMAIN)); + + mSelinuxAutidLogsCollector.setStopRequested(false); // move the clock forward for the limiters. mClock.currentTimeMillis += Duration.ofHours(1).toMillis(); // Ignore what was written in the event logs by previous tests. @@ -74,13 +85,14 @@ public class SelinuxAuditLogsCollectorTest { @After public void tearDown() { + runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides()); mMockitoSession.finishMocking(); } @Test - public void testWriteSdkSandboxAuditLogs() { - writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1"); + public void testWriteAuditLogs() { + writeTestLog("granted", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm1", TEST_DOMAIN, "ttype1", "tclass1"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -91,7 +103,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, true, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -104,7 +116,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm1"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype1", null, @@ -114,9 +126,9 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_multiplePerms() { - writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass"); + public void testWriteAuditLogs_multiplePerms() { + writeTestLog("denied", "perm1 perm2", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm3 perm4", TEST_DOMAIN, "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -127,7 +139,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm1", "perm2"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -140,7 +152,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm3", "perm4"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -150,11 +162,11 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_withPaths() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path"); + public void testWriteAuditLogs_withPaths() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/good/path"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/very/long/path"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/short_path"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "not_a_path"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -165,7 +177,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -178,7 +190,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -191,7 +203,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -204,7 +216,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -214,23 +226,14 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_withCategories() { - writeTestLog( - "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass"); - writeTestLog( - "denied", - "perm", - "sdk_sandbox_audit", - new int[] {123, 456}, - "ttype", - null, - "tclass"); - writeTestLog( - "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass"); + public void testWriteAuditLogs_withCategories() { + writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123}, "ttype", null, "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123, 456}, "ttype", null, "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, null, "ttype", new int[] {666}, "tclass"); writeTestLog( "denied", "perm", - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123, 456}, "ttype", new int[] {666, 777}, @@ -245,7 +248,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "ttype", null, @@ -258,7 +261,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123, 456}, "ttype", null, @@ -271,7 +274,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", new int[] {666}, @@ -284,7 +287,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123, 456}, "ttype", new int[] {666, 777}, @@ -294,11 +297,11 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_withPathAndCategories() { + public void testWriteAuditLogs_withPathAndCategories() { writeTestLog( "denied", "perm", - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "ttype", new int[] {666}, @@ -314,7 +317,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "ttype", new int[] {666}, @@ -324,10 +327,10 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_permissive() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false); + public void testWriteAuditLogs_permissive() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", true); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", false); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -338,7 +341,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -352,7 +355,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -362,7 +365,7 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testNotWriteAuditLogs_notSdkSandbox() { + public void testNotWriteAuditLogs_notTestDomain() { writeTestLog("denied", "perm", "stype", "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -385,15 +388,15 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_upToQuota() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + public void testWriteAuditLogs_upToQuota() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); // These are not pushed. - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -415,14 +418,14 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_resetQuota() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + public void testWriteAuditLogs_resetQuota() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); assertThat(done).isTrue(); @@ -441,11 +444,11 @@ public class SelinuxAuditLogsCollectorTest { anyBoolean()), times(5)); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); // move the clock forward to reset the quota limiter. mClock.currentTimeMillis += Duration.ofHours(1).toMillis(); done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -468,16 +471,16 @@ public class SelinuxAuditLogsCollectorTest { @Test public void testNotWriteAuditLogs_stopRequested() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); // These are not pushed. - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); - mSelinuxAutidLogsCollector.mStopRequested.set(true); + mSelinuxAutidLogsCollector.setStopRequested(true); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); assertThat(done).isFalse(); verify( @@ -495,7 +498,7 @@ public class SelinuxAuditLogsCollectorTest { anyBoolean()), never()); - mSelinuxAutidLogsCollector.mStopRequested.set(false); + mSelinuxAutidLogsCollector.setStopRequested(false); done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); assertThat(done).isTrue(); verify( @@ -516,8 +519,8 @@ public class SelinuxAuditLogsCollectorTest { @Test public void testAuditLogs_resumeJobDoesNotExceedLimit() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - mSelinuxAutidLogsCollector.mStopRequested.set(true); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + mSelinuxAutidLogsCollector.setStopRequested(true); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java new file mode 100644 index 000000000000..2aea8a033f87 --- /dev/null +++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2024 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.server.selinux; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.job.JobParameters; +import android.app.job.JobService; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(AndroidJUnit4.class) +public class SelinuxAuditLogsJobTest { + + private final JobService mJobService = mock(JobService.class); + private final SelinuxAuditLogsCollector mAuditLogsCollector = + mock(SelinuxAuditLogsCollector.class); + private final JobParameters mParams = createJobParameters(666); + private final SelinuxAuditLogsJob mAuditLogsJob = new SelinuxAuditLogsJob(mAuditLogsCollector); + + @Before + public void setUp() { + mAuditLogsCollector.mStopRequested = new AtomicBoolean(); + } + + @Test + public void testFinishSuccessfully() { + when(mAuditLogsCollector.collect(anyInt())).thenReturn(true); + + mAuditLogsJob.start(mJobService, mParams); + + verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + @Test + public void testInterrupt() { + when(mAuditLogsCollector.collect(anyInt())).thenReturn(false); + + mAuditLogsJob.start(mJobService, mParams); + + verify(mJobService, never()).jobFinished(any(), anyBoolean()); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + @Test + public void testInterruptAndResume() { + when(mAuditLogsCollector.collect(anyInt())).thenReturn(false); + mAuditLogsJob.start(mJobService, mParams); + verify(mJobService, never()).jobFinished(any(), anyBoolean()); + + when(mAuditLogsCollector.collect(anyInt())).thenReturn(true); + mAuditLogsJob.start(mJobService, mParams); + verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + @Test + public void testRequestStop() throws InterruptedException { + Semaphore isRunning = new Semaphore(0); + Semaphore stopRequested = new Semaphore(0); + AtomicReference<Throwable> uncaughtException = new AtomicReference<>(); + + // Set up a logs collector that runs in a worker thread until a stop is requested. + when(mAuditLogsCollector.collect(anyInt())) + .thenAnswer( + invocation -> { + assertThat(mAuditLogsCollector.mStopRequested.get()).isFalse(); + isRunning.release(); + stopRequested.acquire(); + assertThat(mAuditLogsCollector.mStopRequested.get()).isTrue(); + return true; + }); + Thread jobThread = + new Thread( + () -> { + mAuditLogsJob.start(mJobService, mParams); + }); + jobThread.setUncaughtExceptionHandler( + (thread, exception) -> uncaughtException.set(exception)); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + jobThread.start(); + + // Wait until the worker thread is running. + isRunning.acquire(); + assertThat(mAuditLogsJob.isRunning()).isTrue(); + + // Request for the worker thread to stop, and wait to verify. + mAuditLogsJob.requestStop(); + stopRequested.release(); + jobThread.join(); + assertThat(uncaughtException.get()).isNull(); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + private static JobParameters createJobParameters(int jobId) { + JobParameters jobParameters = mock(JobParameters.class); + when(jobParameters.getJobId()).thenReturn(jobId); + return jobParameters; + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 5a1785175be7..e727e73f2e72 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -110,6 +110,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -989,6 +990,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn() throws Exception { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); mockManageAccessibilityGranted(mTestableContext); setupShortcutTargetServices(); String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); @@ -1008,6 +1012,9 @@ public class AccessibilityManagerServiceTest { @Test @EnableFlags(Flags.FLAG_ENABLE_HARDWARE_SHORTCUT_DISABLES_WARNING) public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); Settings.Secure.putInt( mTestableContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, @@ -1035,6 +1042,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff() throws Exception { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn(); @@ -1052,6 +1062,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); mockManageAccessibilityGranted(mTestableContext); mA11yms.enableShortcutsForTargets( @@ -1095,6 +1108,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService() throws Exception { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); mockManageAccessibilityGranted(mTestableContext); setupShortcutTargetServices(); @@ -1115,6 +1131,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService() throws Exception { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService(); mA11yms.enableShortcutsForTargets( @@ -1154,6 +1173,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService() throws Exception { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService(); AccessibilityUtils.setAccessibilityServiceState( mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true); @@ -1174,6 +1196,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); mockManageAccessibilityGranted(mTestableContext); mA11yms.enableShortcutsForTargets( @@ -1193,6 +1218,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated(); mA11yms.enableShortcutsForTargets( @@ -1211,6 +1239,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); mockManageAccessibilityGranted(mTestableContext); mA11yms.enableShortcutsForTargets( @@ -1230,6 +1261,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated(); mA11yms.enableShortcutsForTargets( @@ -1249,6 +1283,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); mockManageAccessibilityGranted(mTestableContext); setupShortcutTargetServices(); @@ -1268,6 +1305,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet(); mA11yms.enableShortcutsForTargets( @@ -1286,6 +1326,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); mockManageAccessibilityGranted(mTestableContext); setupShortcutTargetServices(); @@ -1311,6 +1354,9 @@ public class AccessibilityManagerServiceTest { @Test public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() { + // TODO(b/111889696): Remove the user 0 assumption once we support multi-user + Assume.assumeTrue("The test is setup to run as a user 0", + isSameCurrentUser(mA11yms, mTestableContext)); enableShortcutsForTargets_enableQuickSettings_shortcutSet(); mA11yms.enableShortcutsForTargets( @@ -1695,4 +1741,8 @@ public class AccessibilityManagerServiceTest { return mBroadcastReceivers; } } + + private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) { + return service.getCurrentUserIdLocked() == context.getUserId(); + } } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 7c0dbf4889da..c6f3eb357442 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -34,6 +34,7 @@ import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG; import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG; +import static com.android.server.am.UserController.SCHEDULED_STOP_BACKGROUND_USER_MSG; import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG; import static com.android.server.am.UserController.USER_CURRENT_MSG; import static com.android.server.am.UserController.USER_START_MSG; @@ -323,7 +324,8 @@ public class UserControllerTest { @Test public void testStartUserUIDisabled() { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); verify(mInjector, never()).showUserSwitchingDialog( @@ -393,7 +395,8 @@ public class UserControllerTest { @Test public void testFailedStartUserInForeground() { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); mUserController.startUserInForeground(NONEXIST_USER_ID); verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); @@ -470,7 +473,8 @@ public class UserControllerTest { @Test public void testContinueUserSwitch() { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); @@ -483,7 +487,7 @@ public class UserControllerTest { continueAndCompleteUserSwitch(userState, oldUserId, newUserId); verify(mInjector, times(0)).dismissKeyguard(any()); verify(mInjector, times(1)).dismissUserSwitchingDialog(any()); - continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false); + continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false); verifySystemUserVisibilityChangesNeverNotified(); } @@ -491,7 +495,8 @@ public class UserControllerTest { public void testContinueUserSwitchDismissKeyguard() { when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false); mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); @@ -504,14 +509,15 @@ public class UserControllerTest { continueAndCompleteUserSwitch(userState, oldUserId, newUserId); verify(mInjector, times(1)).dismissKeyguard(any()); verify(mInjector, times(1)).dismissUserSwitchingDialog(any()); - continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false); + continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false); verifySystemUserVisibilityChangesNeverNotified(); } @Test public void testContinueUserSwitchUIDisabled() { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); @@ -524,11 +530,11 @@ public class UserControllerTest { // Verify that continueUserSwitch worked as expected continueAndCompleteUserSwitch(userState, oldUserId, newUserId); verify(mInjector, never()).dismissUserSwitchingDialog(any()); - continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false); + continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false); } private void continueUserSwitchAssertions(int expectedOldUserId, int expectedNewUserId, - boolean backgroundUserStopping) { + boolean backgroundUserStopping, boolean expectScheduleBackgroundUserStopping) { Set<Integer> expectedCodes = new LinkedHashSet<>(); expectedCodes.add(COMPLETE_USER_SWITCH_MSG); expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); @@ -536,6 +542,9 @@ public class UserControllerTest { expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG); expectedCodes.add(0); // this is for directly posting in stopping. } + if (expectScheduleBackgroundUserStopping) { + expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG); + } Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); assertEquals("Unexpected message sent", expectedCodes, actualCodes); Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG); @@ -571,6 +580,112 @@ public class UserControllerTest { ).collect(Collectors.toList()), Collections.emptySet()); } + /** Test scheduling stopping of background users after a user-switch. */ + @Test + public void testScheduleStopOfBackgroundUser_switch() { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER); + + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ 2); + + setUpUser(TEST_USER_ID1, NO_USERINFO_FLAGS); + + // Switch to TEST_USER_ID from user 0 + int numberOfUserSwitches = 0; + addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, + ++numberOfUserSwitches, + /* expectOldUserStopping= */false, + /* expectScheduleBackgroundUserStopping= */ false); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID), + mUserController.getRunningUsersLU()); + + // Allow the post-switch processing to complete (there should be no scheduled stopping). + assertAndProcessScheduledStopBackgroundUser(false, null); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID), + mUserController.getRunningUsersLU()); + + // Switch to TEST_USER_ID1 from TEST_USER_ID + addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID, + ++numberOfUserSwitches, + /* expectOldUserStopping= */false, + /* expectScheduleBackgroundUserStopping= */ true); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1), + mUserController.getRunningUsersLU()); + + // Switch back to TEST_USER_ID from TEST_USER_ID1 + addForegroundUserAndContinueUserSwitch(TEST_USER_ID, TEST_USER_ID1, + ++numberOfUserSwitches, + /* expectOldUserStopping= */false, + /* expectScheduleBackgroundUserStopping= */ true); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID), + mUserController.getRunningUsersLU()); + + // Allow the post-switch processing to complete. + assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID); + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1); + assertAndProcessScheduledStopBackgroundUser(false, null); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID), + mUserController.getRunningUsersLU()); + } + + /** Test scheduling stopping of background users that were started in the background. */ + @Test + public void testScheduleStopOfBackgroundUser_startInBackground() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER); + + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ 2); + + // Start two full background users (which should both get scheduled for stopping) + // and one profile (which should not). + setUpAndStartUserInBackground(TEST_USER_ID); + setUpAndStartUserInBackground(TEST_USER_ID1); + setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED); + + assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1, TEST_USER_ID2), + new HashSet<>(mUserController.getRunningUsersLU())); + + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID); + assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID2), + new HashSet<>(mUserController.getRunningUsersLU())); + + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1); + assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2), + new HashSet<>(mUserController.getRunningUsersLU())); + + assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID2); + assertAndProcessScheduledStopBackgroundUser(false, null); + + // Now that we've processed the stops, let's make sure that a subsequent one will work too. + setUpAndStartUserInBackground(TEST_USER_ID3); + assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2, TEST_USER_ID3), + new HashSet<>(mUserController.getRunningUsersLU())); + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID3); + assertAndProcessScheduledStopBackgroundUser(false, null); + assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2), + new HashSet<>(mUserController.getRunningUsersLU())); + } + + /** + * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected. + * @param userId the user we are checking to see whether it is scheduled. + * Can be null, when expectScheduled is false, to indicate no user should be + * scheduled. + */ + private void assertAndProcessScheduledStopBackgroundUser( + boolean expectScheduled, @Nullable Integer userId) { + TestHandler handler = mInjector.mHandler; + if (expectScheduled) { + assertTrue(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId)); + handler.removeMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId); + mUserController.processScheduledStopOfBackgroundUser(userId); + } else { + assertFalse(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId)); + } + } + @Test public void testExplicitSystemUserStartInBackground() { setUpUser(UserHandle.USER_SYSTEM, 0); @@ -587,13 +702,14 @@ public class UserControllerTest { public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking() throws InterruptedException, RemoteException { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); int numberOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); // running: user 0, USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}), @@ -601,7 +717,7 @@ public class UserControllerTest { numberOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); // running: user 0, USER_ID, USER_ID1 assertFalse(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}), @@ -609,7 +725,7 @@ public class UserControllerTest { numberOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); UserState ussUser2 = mUserStates.get(TEST_USER_ID2); // skip middle step and call this directly. mUserController.finishUserSwitch(ussUser2); @@ -631,13 +747,14 @@ public class UserControllerTest { public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode() throws Exception { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); int numberOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); // running: user 0, USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}), @@ -645,7 +762,7 @@ public class UserControllerTest { numberOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID, - numberOfUserSwitches, true); + numberOfUserSwitches, true, false); // running: user 0, USER_ID1 // stopped + unlocked: USER_ID numberOfUserSwitches++; @@ -663,7 +780,7 @@ public class UserControllerTest { .lockCeStorage(anyInt()); addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1, - numberOfUserSwitches, true); + numberOfUserSwitches, true, false); // running: user 0, USER_ID2 // stopped + unlocked: USER_ID1 // stopped + locked: USER_ID @@ -686,7 +803,8 @@ public class UserControllerTest { public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped() throws Exception { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); final int PARENT_ID = 200; final int PROFILE1_ID = 201; @@ -707,7 +825,7 @@ public class UserControllerTest { int numberOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); mUserController.finishUserSwitch(mUserStates.get(PARENT_ID)); waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); assertTrue(mUserController.canStartMoreUsers()); @@ -722,7 +840,7 @@ public class UserControllerTest { numberOfUserSwitches++; addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID)); waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); assertTrue(mUserController.canStartMoreUsers()); @@ -747,7 +865,7 @@ public class UserControllerTest { numberOfUserSwitches++; addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); mUserController.finishUserSwitch(mUserStates.get(PARENT_ID)); waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); // We've now done a user switch and should notice that we've exceeded the maximum number of @@ -766,7 +884,8 @@ public class UserControllerTest { @Test public void testRunningUsersListOrder_parentAfterProfile() { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); final int PARENT_ID = 200; final int PROFILE1_ID = 201; @@ -787,7 +906,7 @@ public class UserControllerTest { int numberOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); assertEquals(Arrays.asList( new Integer[] {SYSTEM_USER_ID, PARENT_ID}), mUserController.getRunningUsersLU()); @@ -799,7 +918,7 @@ public class UserControllerTest { numberOfUserSwitches++; addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID, - numberOfUserSwitches, false); + numberOfUserSwitches, false, false); assertEquals(Arrays.asList( new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID}), mUserController.getRunningUsersLU()); @@ -827,7 +946,8 @@ public class UserControllerTest { @Test public void testRunningUsersListOrder_currentAtEnd() { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); final int CURRENT_ID = 200; final int PROFILE_ID = 201; @@ -842,7 +962,7 @@ public class UserControllerTest { new Integer[] {SYSTEM_USER_ID}), mUserController.getRunningUsersLU()); - addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false); + addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false, false); assertEquals(Arrays.asList( new Integer[] {SYSTEM_USER_ID, CURRENT_ID}), mUserController.getRunningUsersLU()); @@ -864,7 +984,8 @@ public class UserControllerTest { @Test public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); setUpAndStartUserInBackground(TEST_USER_ID); assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true, @@ -922,7 +1043,8 @@ public class UserControllerTest { @Test public void testUserLockingForDelayedLockingMode() throws Exception { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); // allowDelayedLocking set and no KeyEvictedCallback, so it should not lock. setUpAndStartUserInBackground(TEST_USER_ID); @@ -973,7 +1095,8 @@ public class UserControllerTest { @Test public void testStopProfile_doesNotStopItsParent() throws Exception { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); final Range<Integer> RUNNING_RANGE = Range.closed(UserState.STATE_BOOTING, UserState.STATE_RUNNING_UNLOCKED); @@ -1053,7 +1176,8 @@ public class UserControllerTest { @Test public void testStopPrivateProfile() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); @@ -1071,7 +1195,8 @@ public class UserControllerTest { @Test public void testStopPrivateProfileWithDelayedLocking() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); @@ -1083,7 +1208,8 @@ public class UserControllerTest { @Test public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.disableFlags( @@ -1113,7 +1239,8 @@ public class UserControllerTest { public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, - /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); @@ -1130,7 +1257,8 @@ public class UserControllerTest { @Test public void testStopManagedProfileWithDelayedLocking() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); @@ -1285,7 +1413,8 @@ public class UserControllerTest { public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception { // enable user switch ui, because keyguard is only shown then mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); // mock the device to be secure in order to expect the keyguard to be shown when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); @@ -1365,7 +1494,8 @@ public class UserControllerTest { } private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId, - int expectedNumberOfCalls, boolean expectOldUserStopping) { + int expectedNumberOfCalls, boolean expectOldUserStopping, + boolean expectScheduleBackgroundUserStopping) { // Start user -- this will update state of mUserController mUserController.startUser(newUserId, USER_START_MODE_FOREGROUND); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); @@ -1378,8 +1508,12 @@ public class UserControllerTest { mInjector.mHandler.clearAllRecordedMessages(); // Verify that continueUserSwitch worked as expected continueAndCompleteUserSwitch(userState, oldUserId, newUserId); + assertEquals(mInjector.mHandler + .hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId), + expectScheduleBackgroundUserStopping); verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any()); - continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping); + continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping, + expectScheduleBackgroundUserStopping); } private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java index 3f5217c371de..8ca862390a65 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.pm.PackageManagerInternal; import android.frameworks.vibrator.ScaleParam; +import android.frameworks.vibrator.VibrationParam; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -59,6 +60,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -177,6 +180,24 @@ public class VibratorControlServiceTest { verifyZeroInteractions(mMockVibrationScaler); } + @Test(expected = IllegalArgumentException.class) + public void testOnRequestVibrationParamsComplete_withNullVibrationParams_throwsException() { + mVibratorControlService.registerVibratorController(mFakeVibratorController); + int timeoutInMillis = 10; + CompletableFuture<Void> unusedFuture = + mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE, + timeoutInMillis); + IBinder token = mVibratorControlService.getRequestVibrationParamsToken(); + + List<VibrationParam> vibrationParamList = Arrays.asList( + VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f), + null, + VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f)); + + mVibratorControlService.onRequestVibrationParamsComplete(token, + vibrationParamList.toArray(new VibrationParam[0])); + } + @Test public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() { mVibratorControlService.registerVibratorController(mFakeVibratorController); @@ -214,6 +235,19 @@ public class VibratorControlServiceTest { verifyZeroInteractions(mMockVibrationScaler); } + @Test(expected = IllegalArgumentException.class) + public void testSetVibrationParams_withNullVibrationParams_throwsException() { + mVibratorControlService.registerVibratorController(mFakeVibratorController); + List<VibrationParam> vibrationParamList = Arrays.asList( + VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f), + null, + VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f)); + + mVibratorControlService.setVibrationParams( + vibrationParamList.toArray(new VibrationParam[0]), + mFakeVibratorController); + } + @Test public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() { mVibratorControlService.registerVibratorController(mFakeVibratorController); diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java index a606388da190..c17d11e51497 100644 --- a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java +++ b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java @@ -42,7 +42,10 @@ public final class VibrationParamGenerator { return vibrationParamList.toArray(new VibrationParam[0]); } - private static VibrationParam generateVibrationParam(int type, float scale) { + /** + * Generates a {@link VibrationParam} with the specified type and scale. + */ + public static VibrationParam generateVibrationParam(int type, float scale) { ScaleParam scaleParam = new ScaleParam(); scaleParam.typesMask = type; scaleParam.scale = scale; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 2e80bc721c7f..aa780edffc60 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -130,7 +130,6 @@ import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -4179,13 +4178,8 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - @Ignore // TODO(b/330888878): fix test in main - public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) + public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() { // Set up portrait close to square display setUpDisplaySizeWithApp(2200, 2280); final DisplayContent display = mActivity.mDisplayContent; @@ -4198,16 +4192,21 @@ public class SizeCompatTests extends WindowTestsBase { .setInsetsSize(Insets.of(0, 0, 0, 150)) }; display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs); - assertTrue(navbar.providesDisplayDecorInsets() - && display.getDisplayPolicy().updateDecorInsetsInfo()); + assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo()); display.sendNewConfiguration(); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); - // Activity is fullscreen even though orientation is not respected with insets, because - // the display still matches or is less than the activity aspect ratio - assertEquals(display.getBounds(), mActivity.getBounds()); - assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + final Rect bounds = activity.getBounds(); + // Activity should be letterboxed and should have portrait app bounds + assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertTrue(bounds.height() > bounds.width()); } @Test @@ -4229,6 +4228,7 @@ public class SizeCompatTests extends WindowTestsBase { // can be aligned inside parentAppBounds assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200)); } + @Test public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() { if (Flags.insetsDecoupledConfiguration()) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 1ca808f4153a..225e85e03b26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -833,8 +833,11 @@ public class TaskTests extends WindowTestsBase { final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); final ActivityRecord.CompatDisplayInsets compatInsets = new ActivityRecord.CompatDisplayInsets( - display, activity, /* fixedOrientationBounds= */ null); - task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets); + display, activity, /* letterboxedContainerBounds */ null, + /* useOverrideInsets */ false); + final TaskFragment.ConfigOverrideHint overrideHint = new TaskFragment.ConfigOverrideHint(); + overrideHint.mTmpCompatInsets = compatInsets; + task.computeConfigResourceOverrides(inOutConfig, parentConfig, overrideHint); assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 72bedf2da21f..5b1a18da3173 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -410,6 +410,8 @@ public class WallpaperControllerTests extends WindowTestsBase { final WindowState wallpaperWindow = createWallpaperWindow(dc); final WallpaperWindowToken token = wallpaperWindow.mToken.asWallpaperToken(); wallpaperWindow.setHasSurface(true); + spyOn(dc.mWallpaperController); + doReturn(wallpaperWindow).when(dc.mWallpaperController).getWallpaperTarget(); // Set-up mock shell transitions registerTestTransitionPlayer(); diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 1fdf97a4c821..093923f3ed53 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -45,13 +45,13 @@ import android.os.test.TestLooper; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.util.AtomicFile; +import android.util.LongArrayQueue; import android.util.Xml; -import android.utils.LongArrayQueue; -import android.utils.XmlUtils; import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.PackageWatchdog.HealthCheckState; diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml index b23a87e57754..fa352cf1e832 100644 --- a/tests/TouchLatency/app/src/main/res/values/styles.xml +++ b/tests/TouchLatency/app/src/main/res/values/styles.xml @@ -18,6 +18,7 @@ <!-- Base application theme. --> <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <!-- Customize your theme here. --> + <item name="android:windowLayoutInDisplayCutoutMode">default</item> </style> </resources> |