Merge "Manual brightness working as expected with refactoring flag enabled" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index eac416a..24cd610 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -641,6 +641,19 @@
],
}
+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/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 613678b..410074e 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 @@
mAdminProtectedPackages.put(userId, packageNames);
}
}
+ if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+ postCheckIdleStates(userId);
+ }
}
@Override
diff --git a/config/Android.bp b/config/Android.bp
index 6a6f848..dd681ca 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,3 +33,9 @@
name: "preloaded-classes-denylist",
srcs: ["preloaded-classes-denylist"],
}
+
+prebuilt_etc {
+ name: "dirty-image-objects",
+ src: "dirty-image-objects",
+ filename: "dirty-image-objects",
+}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0dab3de..5e9fdfb 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1349,12 +1349,26 @@
public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
/**
- * The most restricted level where the apps are considered "in-hibernation",
- * its package visibility to the rest of the system is limited.
+ * The restricted level where the apps are in a force-stopped state.
*
* @hide
*/
- public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+ public static final int RESTRICTION_LEVEL_FORCE_STOPPED = 60;
+
+ /**
+ * The heavily background restricted level, where apps cannot start without an explicit
+ * launch by the user.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_USER_LAUNCH_ONLY = 70;
+
+ /**
+ * A reserved restriction level that is not well-defined.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_CUSTOM = 90;
/**
* Not a valid restriction level, it defines the maximum numerical value of restriction level.
@@ -1371,12 +1385,116 @@
RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
RESTRICTION_LEVEL_RESTRICTED_BUCKET,
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
- RESTRICTION_LEVEL_HIBERNATION,
+ RESTRICTION_LEVEL_FORCE_STOPPED,
+ RESTRICTION_LEVEL_USER_LAUNCH_ONLY,
+ RESTRICTION_LEVEL_CUSTOM,
RESTRICTION_LEVEL_MAX,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RestrictionLevel{}
+ /**
+ * Maximum string length for sub reason for restriction.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16;
+
+ /**
+ * Restriction reason unknown - do not use directly.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_UNKNOWN = 0;
+
+ /**
+ * Restriction reason to be used when this is normal behavior for the state.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DEFAULT = 1;
+
+ /**
+ * Restriction reason is some kind of timeout that moves the app to a more restricted state.
+ * The threshold should specify how long the app was dormant, in milliseconds.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DORMANT = 2;
+
+ /**
+ * Restriction reason to be used when removing a restriction due to direct or indirect usage
+ * of the app, especially to undo any automatic restrictions.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USAGE = 3;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app, through
+ * UI or command line interface.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER = 4;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app on being
+ * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing
+ * high battery drain or affecting system performance and the OS recommends that the user
+ * restrict the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER_NUDGED = 5;
+
+ /**
+ * Restriction reason to be used when the OS automatically detects that the app is causing
+ * system health issues such as performance degradation, battery drain, high memory usage, etc.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6;
+
+ /**
+ * Restriction reason to be used when there is a server-side decision made to restrict an app
+ * that is showing widespread problems on user devices, or violating policy in some way.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7;
+
+ /**
+ * Restriction reason to be used when some other problem requires restricting the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_OTHER = 8;
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_REASON_" }, value = {
+ RESTRICTION_REASON_UNKNOWN,
+ RESTRICTION_REASON_DEFAULT,
+ RESTRICTION_REASON_DORMANT,
+ RESTRICTION_REASON_USAGE,
+ RESTRICTION_REASON_USER,
+ RESTRICTION_REASON_USER_NUDGED,
+ RESTRICTION_REASON_SYSTEM_HEALTH,
+ RESTRICTION_REASON_REMOTE_TRIGGER,
+ RESTRICTION_REASON_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionReason{}
+
/** @hide */
@android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
@@ -1393,12 +1511,16 @@
return "restricted_bucket";
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return "background_restricted";
- case RESTRICTION_LEVEL_HIBERNATION:
- return "hibernation";
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
+ return "stopped";
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY:
+ return "user_only";
+ case RESTRICTION_LEVEL_CUSTOM:
+ return "custom";
case RESTRICTION_LEVEL_MAX:
return "max";
default:
- return "";
+ return String.valueOf(level);
}
}
@@ -6127,6 +6249,43 @@
}
/**
+ * Requests the system to log the reason for restricting/unrestricting an app. This API
+ * should be called before applying any change to the restriction level.
+ * <p>
+ * The {@code enabled} value determines whether the state is being applied or removed.
+ * Not all restrictions are actual restrictions. For example,
+ * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
+ * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
+ * app is being put in a power-save allowlist.
+ *
+ * @param packageName the package name of the app
+ * @param uid the uid of the app
+ * @param restrictionLevel the restriction level specified in {@code RestrictionLevel}
+ * @param enabled whether the state is being applied or removed
+ * @param reason the reason for the restriction state change, from {@code RestrictionReason}
+ * @param subReason a string sub reason limited to 16 characters that specifies additional
+ * information about the reason for restriction.
+ * @param threshold for reasons that are due to exceeding some threshold, the threshold value
+ * must be specified. The unit of the threshold depends on the reason and/or
+ * subReason. For time, use milliseconds. For memory, use KB. For count, use
+ * the actual count or normalized as per-hour. For power, use milliwatts. Etc.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.DEVICE_POWER)
+ public void noteAppRestrictionEnabled(@NonNull String packageName, int uid,
+ @RestrictionLevel int restrictionLevel, boolean enabled,
+ @RestrictionReason int reason,
+ @Nullable String subReason, long threshold) {
+ try {
+ getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled,
+ reason, subReason, threshold);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies {@link #getRunningAppProcesses app processes} that the system properties
* have changed.
*
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index dca164d..3765c81 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1009,4 +1009,12 @@
* @param originatingUid The UID of the instrumented app that initialized the override
*/
void clearAllOverridePermissionStates(int originatingUid);
+
+ /**
+ * Request the system to log the reason for restricting / unrestricting an app.
+ * @see ActivityManager#noteAppRestrictionEnabled
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
+ void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
+ boolean enabled, int reason, in String subReason, long threshold);
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 1aee9fe..a9f2d74 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -317,11 +317,6 @@
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 6a07484..ac843cb 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -195,6 +195,25 @@
}
}
+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 {
+ 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/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f5bff9d..4c0da7c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4844,16 +4844,6 @@
public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
"android.hardware.rotaryencoder.lowres";
- /**
- * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
- * support for contextual search helper.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.FEATURE)
- public static final String FEATURE_CONTEXTUAL_SEARCH_HELPER =
- "android.software.contextualsearch";
-
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 4963a4f..321e539 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -171,6 +171,13 @@
}
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/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 4a4d451..7b452a8 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -74,7 +74,7 @@
* and {@link HardwareBuffer.Format} is supported on the device.
*
* @return True if the device can support efficiently compositing the content described by the
- * dataspace and format. False if GPOU composition fallback is otherwise required.
+ * dataspace and format. False if GPU composition fallback is otherwise required.
*/
@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public boolean isCombinationSupported(@DataSpace.ColorDataSpace int dataspace,
@@ -135,7 +135,6 @@
private static native long nGetDestructor();
private static native long nCreateDefault();
- private static native boolean nSupportFp16ForHdr(long nativeObject);
private static native boolean nSupportMixedColorSpaces(long nativeObject);
private static native boolean nIsCombinationSupported(
long nativeObject, int dataspace, int format);
diff --git a/core/java/android/hardware/face/FaceCallback.java b/core/java/android/hardware/face/FaceCallback.java
new file mode 100644
index 0000000..b69024f
--- /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 210ce2b..2592630 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -37,9 +37,9 @@
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.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 @@
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 final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
+ private class FaceServiceReceiver extends IFaceServiceReceiver.Stub {
+ private final FaceCallback mFaceCallback;
+
+ 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 @@
@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 @@
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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 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 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 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 @@
}
}
- 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 0000000..e4fbe6e
--- /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 81e321d..37f2fb2 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_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.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 @@
@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 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 @@
* @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 @@
*
* @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 @@
*
* @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 @@
/**
* @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 @@
*/
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 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 @@
@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 @@
}
}
- 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 @@
}
}
- private void sendUdfpsOverlayShown() {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsOverlayShown();
- }
- }
-
/**
* @hide
*/
@@ -1562,7 +1341,6 @@
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 @@
}
});
}
+ mHandler = context.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
private int getCurrentUserId() {
@@ -1773,66 +1553,72 @@
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 4ae0a57..3b59901 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -98,6 +98,8 @@
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 fe3fa8c..2daf4ac 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1675,6 +1675,21 @@
}
/**
+ * 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/provider/Settings.java b/core/java/android/provider/Settings.java
index dd93972..6ad7422 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9020,6 +9020,16 @@
"accessibility_display_daltonizer";
/**
+ * Integer property that determines the saturation level of color correction. Default value
+ * is defined in Settings config.xml.
+ * [0-10] inclusive where 0 would look as if color space adustment is not applied at all.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL =
+ "accessibility_display_daltonizer_saturation_level";
+
+ /**
* Setting that specifies whether automatic click when the mouse pointer stops moving is
* enabled.
*
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index 1a6cf88..ddb662ad 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -28,6 +28,8 @@
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/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 40366c2..8d55777 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.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.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 @@
// 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 @@
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 @@
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 @@
}
}
+ 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(
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 12ce0f4..bdfc236 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -27,6 +27,7 @@
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 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1231,6 +1234,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1260,6 +1265,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
int interactionId) {
int interactionIdWaitingForPrefetchResultCopy = -1;
@@ -1324,6 +1330,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
@@ -1372,6 +1380,7 @@
* @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 @@
* @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 fe59519..a9e5db5 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -34,6 +34,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 1841353..52487fb 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2486,9 +2486,6 @@
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/jni/Android.bp b/core/jni/Android.bp
index 773823d..80a7599 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -82,6 +82,14 @@
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
+ "android_view_InputDevice.cpp",
+ "android_view_KeyCharacterMap.cpp",
+ "android_view_KeyEvent.cpp",
+ "android_view_MotionEvent.cpp",
+ "android_view_Surface.cpp",
+ "android_view_VelocityTracker.cpp",
+ "android_view_VerifiedKeyEvent.cpp",
+ "android_view_VerifiedMotionEvent.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
"core_jni_helpers.cpp",
":deviceproductinfoconstants_aidl",
@@ -158,16 +166,11 @@
"android_view_CompositionSamplingListener.cpp",
"android_view_DisplayEventReceiver.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
"android_view_InputQueue.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
- "android_view_MotionEvent.cpp",
"android_view_MotionPredictor.cpp",
"android_view_PointerIcon.cpp",
- "android_view_Surface.cpp",
"android_view_SurfaceControl.cpp",
"android_view_SurfaceControlHdrLayerInfoListener.cpp",
"android_view_WindowManagerGlobal.cpp",
@@ -175,9 +178,6 @@
"android_view_SurfaceSession.cpp",
"android_view_TextureView.cpp",
"android_view_TunnelModeEnabledListener.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_text_Hyphenator.cpp",
"android_os_Debug.cpp",
"android_os_GraphicsEnvironment.cpp",
@@ -394,7 +394,8 @@
"-Wno-unused-function",
],
srcs: [
- "LayoutlibLoader.cpp",
+ "platform/host/HostRuntime.cpp",
+ "platform/host/native_window_jni.cpp",
],
include_dirs: [
"external/vulkan-headers/include",
@@ -414,6 +415,7 @@
"libhostgraphics",
"libhwui",
"libimage_type_recognition",
+ "libinput",
"libjpeg",
"libpiex",
"libpng",
@@ -441,16 +443,9 @@
"android_os_MessageQueue.cpp",
"android_os_Parcel.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
- "android_view_MotionEvent.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
@@ -458,7 +453,6 @@
"android_util_FileObserver.cpp",
],
static_libs: [
- "libinput",
"libbinderthreadstateutils",
"libsqlite",
"libgui_window_info_static",
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b801a69..3e3af40 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -28,6 +28,8 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
+#include <sstream>
+
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -598,8 +600,8 @@
// ----------------- @CriticalNative ------------------------------
-static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
- jboolean keepHistory) {
+static jlong android_view_MotionEvent_nativeCopy(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jboolean keepHistory) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -609,8 +611,8 @@
return reinterpret_cast<jlong>(destEvent);
}
-static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
- jint idBits) {
+static jlong android_view_MotionEvent_nativeSplit(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jint idBits) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -621,168 +623,192 @@
return reinterpret_cast<jlong>(destEvent);
}
-static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getId();
}
-static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDeviceId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDeviceId();
}
-static jint android_view_MotionEvent_nativeGetSource(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getSource();
}
-static void android_view_MotionEvent_nativeSetSource(jlong nativePtr, jint source) {
+static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint source) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setSource(source);
}
-static jint android_view_MotionEvent_nativeGetDisplayId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDisplayId();
}
-static void android_view_MotionEvent_nativeSetDisplayId(jlong nativePtr, jint displayId) {
+static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint displayId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->setDisplayId(displayId);
}
-static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getAction();
}
-static void android_view_MotionEvent_nativeSetAction(jlong nativePtr, jint action) {
+static void android_view_MotionEvent_nativeSetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint action) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setAction(action);
}
-static int android_view_MotionEvent_nativeGetActionButton(jlong nativePtr) {
+static int android_view_MotionEvent_nativeGetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getActionButton();
}
-static void android_view_MotionEvent_nativeSetActionButton(jlong nativePtr, jint button) {
+static void android_view_MotionEvent_nativeSetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint button) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setActionButton(button);
}
-static jboolean android_view_MotionEvent_nativeIsTouchEvent(jlong nativePtr) {
+static jboolean android_view_MotionEvent_nativeIsTouchEvent(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->isTouchEvent();
}
-static jint android_view_MotionEvent_nativeGetFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getFlags();
}
-static void android_view_MotionEvent_nativeSetFlags(jlong nativePtr, jint flags) {
+static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint flags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setFlags(flags);
}
-static jint android_view_MotionEvent_nativeGetEdgeFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getEdgeFlags();
}
-static void android_view_MotionEvent_nativeSetEdgeFlags(jlong nativePtr, jint edgeFlags) {
+static void android_view_MotionEvent_nativeSetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint edgeFlags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setEdgeFlags(edgeFlags);
}
-static jint android_view_MotionEvent_nativeGetMetaState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetMetaState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getMetaState();
}
-static jint android_view_MotionEvent_nativeGetButtonState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetButtonState(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getButtonState();
}
-static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint buttonState) {
+static void android_view_MotionEvent_nativeSetButtonState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint buttonState) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setButtonState(buttonState);
}
-static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetClassification(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return static_cast<jint>(event->getClassification());
}
-static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
- jfloat deltaY) {
+static void android_view_MotionEvent_nativeOffsetLocation(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat deltaX, jfloat deltaY) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->offsetLocation(deltaX, deltaY);
}
-static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawXOffset();
}
-static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawYOffset();
}
-static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetYPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetXCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXCursorPosition();
}
-static jfloat android_view_MotionEvent_nativeGetYCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYCursorPosition();
}
-static void android_view_MotionEvent_nativeSetCursorPosition(jlong nativePtr, jfloat x, jfloat y) {
+static void android_view_MotionEvent_nativeSetCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jfloat x, jfloat y) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setCursorPosition(x, y);
}
-static jlong android_view_MotionEvent_nativeGetDownTimeNanos(jlong nativePtr) {
+static jlong android_view_MotionEvent_nativeGetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDownTime();
}
-static void android_view_MotionEvent_nativeSetDownTimeNanos(jlong nativePtr, jlong downTimeNanos) {
+static void android_view_MotionEvent_nativeSetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jlong downTimeNanos) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setDownTime(downTimeNanos);
}
-static jint android_view_MotionEvent_nativeGetPointerCount(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetPointerCount(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getPointerCount());
}
-static jint android_view_MotionEvent_nativeFindPointerIndex(jlong nativePtr, jint pointerId) {
+static jint android_view_MotionEvent_nativeFindPointerIndex(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint pointerId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->findPointerIndex(pointerId));
}
-static jint android_view_MotionEvent_nativeGetHistorySize(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetHistorySize(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getHistorySize());
}
-static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) {
+static void android_view_MotionEvent_nativeScale(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat scale) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->scale(scale);
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 869b53d..ac6298d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -27,15 +27,19 @@
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <android_runtime/android_view_Surface.h>
#include <android_runtime/Log.h>
+#ifdef __ANDROID__
#include <private/android/AHardwareBufferHelpers.h>
#include "android_os_Parcel.h"
#include <binder/Parcel.h>
#include <gui/BLASTBufferQueue.h>
+#endif
#include <gui/Surface.h>
+#ifdef __ANDROID__
#include <gui/SurfaceControl.h>
#include <gui/view/Surface.h>
+#endif
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>
@@ -67,6 +71,7 @@
jfieldID bottom;
} gRectClassInfo;
+#ifdef __ANDROID__
class JNamedColorSpace {
public:
// ColorSpace.Named.SRGB.ordinal() = 0;
@@ -84,6 +89,7 @@
return ui::Dataspace::V0_SRGB;
}
}
+#endif
// ----------------------------------------------------------------------------
@@ -144,6 +150,7 @@
// ----------------------------------------------------------------------------
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
jobject surfaceTextureObj) {
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
@@ -162,6 +169,7 @@
surface->incStrong(&sRefBaseOwner);
return jlong(surface.get());
}
+#endif
static void nativeRelease(JNIEnv* env, jclass clazz, jlong nativeObject) {
sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
@@ -269,7 +277,7 @@
}
// ----------------------------------------------------------------------------
-
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
jlong surfaceControlNativeObj) {
sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
@@ -380,6 +388,7 @@
// to the Parcel
surfaceShim.writeToParcel(parcel, /*nameAlreadyWritten*/true);
}
+#endif
static jint nativeGetWidth(JNIEnv* env, jclass clazz, jlong nativeObject) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -412,6 +421,7 @@
return surface->disconnect(-1, IGraphicBufferProducer::DisconnectMode::AllLocal);
}
+#ifdef __ANDROID__
static jint nativeAttachAndQueueBufferWithColorSpace(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject hardwareBuffer, jint colorSpaceId) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -422,6 +432,7 @@
fromNamedColorSpaceValueToDataspace(colorSpaceId));
return err;
}
+#endif
static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject,
jboolean enabled) {
@@ -457,8 +468,10 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceMethods[] = {
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
(void*)nativeCreateFromSurfaceTexture},
+#endif
{"nativeRelease", "(J)V", (void*)nativeRelease},
{"nativeIsValid", "(J)Z", (void*)nativeIsValid},
{"nativeIsConsumerRunningBehind", "(J)Z", (void*)nativeIsConsumerRunningBehind},
@@ -467,21 +480,27 @@
{"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
(void*)nativeUnlockCanvasAndPost},
{"nativeAllocateBuffers", "(J)V", (void*)nativeAllocateBuffers},
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl},
{"nativeGetFromSurfaceControl", "(JJ)J", (void*)nativeGetFromSurfaceControl},
{"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel},
{"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
{"nativeGetWidth", "(J)I", (void*)nativeGetWidth},
{"nativeGetHeight", "(J)I", (void*)nativeGetHeight},
{"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber},
{"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode},
{"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
+#ifdef __ANDROID__
{"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
(void*)nativeAttachAndQueueBufferWithColorSpace},
+#endif
{"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
{"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
{"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
+#ifdef __ANDROID__
{"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+#endif
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
};
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 637b823..23cedb8 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -19,6 +19,7 @@
#include <android/native_window.h>
#include <ui/PublicFormat.h>
+#include <utils/StrongPointer.h>
#include "jni.h"
diff --git a/core/jni/platform/OWNERS b/core/jni/platform/OWNERS
new file mode 100644
index 0000000..10ce5cf
--- /dev/null
+++ b/core/jni/platform/OWNERS
@@ -0,0 +1,4 @@
+include /graphics/java/android/graphics/OWNERS
+
+diegoperez@google.com
+jgaillard@google.com
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/platform/host/HostRuntime.cpp
similarity index 94%
rename from core/jni/LayoutlibLoader.cpp
rename to core/jni/platform/host/HostRuntime.cpp
index 83b6afa..0433855 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -80,7 +80,7 @@
namespace android {
-extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
+extern int register_android_animation_PropertyValuesHolder(JNIEnv* env);
extern int register_android_content_AssetManager(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
@@ -106,15 +106,17 @@
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
-extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
+extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv* env);
-#define REG_JNI(name) { name }
+#define REG_JNI(name) \
+ { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
-// Map of all possible class names to register to their corresponding JNI registration function pointer
-// The actual list of registered classes will be determined at runtime via the 'native_classes' System property
+// Map of all possible class names to register to their corresponding JNI registration function
+// pointer The actual list of registered classes will be determined at runtime via the
+// 'native_classes' System property
static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.animation.PropertyValuesHolder",
REG_JNI(register_android_animation_PropertyValuesHolder)},
@@ -154,8 +156,7 @@
};
static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
- const vector<string>& classesToRegister, JNIEnv* env) {
-
+ const vector<string>& classesToRegister, JNIEnv* env) {
for (const string& className : classesToRegister) {
if (jniRegMap.at(className).mProc(env) < 0) {
return -1;
@@ -169,15 +170,14 @@
return 0;
}
-int AndroidRuntime::registerNativeMethods(JNIEnv* env,
- const char* className, const JNINativeMethod* gMethods, int numMethods) {
+int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods, int numMethods) {
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
JNIEnv* AndroidRuntime::getJNIEnv() {
JNIEnv* env;
- if (javaVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK)
- return nullptr;
+ if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
return env;
}
@@ -186,11 +186,10 @@
}
static vector<string> parseCsv(const string& csvString) {
- vector<string> result;
+ vector<string> result;
istringstream stream(csvString);
string segment;
- while(getline(stream, segment, ','))
- {
+ while (getline(stream, segment, ',')) {
result.push_back(segment);
}
return result;
@@ -274,7 +273,9 @@
}
struct CloseHandleWrapper {
- void operator()(HANDLE h) { CloseHandle(h); }
+ void operator()(HANDLE h) {
+ CloseHandle(h);
+ }
};
std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
@@ -384,8 +385,9 @@
// Configuration is stored as java System properties.
// Get a reference to System.getProperty
jclass system = FindClassOrDie(env, "java/lang/System");
- jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
- "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ jmethodID getPropertyMethod =
+ GetStaticMethodIDOrDie(env, system, "getProperty",
+ "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
// Java system properties that contain LayoutLib config. The initial values in the map
// are the default values if the property is not specified.
diff --git a/core/jni/platform/host/native_window_jni.cpp b/core/jni/platform/host/native_window_jni.cpp
new file mode 100644
index 0000000..c17c480
--- /dev/null
+++ b/core/jni/platform/host/native_window_jni.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android_runtime/android_view_Surface.h>
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+
+using namespace android;
+
+ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
+ sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
+ if (win != NULL) {
+ ANativeWindow_acquire(win.get());
+ }
+ return win.get();
+}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5ae365c..f5bbbb4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -103,6 +103,7 @@
optional SettingProto accessibility_pinch_to_zoom_anywhere_enabled = 55 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_single_finger_panning_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_floating_menu_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b262ebd..0df49dc 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 a622d36..1a61870 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/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 7c9f2ef..a248ede 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -235,6 +235,12 @@
<integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
<java-symbol type="integer" name="config_esim_bootstrap_data_limit_bytes" />
+ <!-- Reevaluate existing data networks for bootstrap sim data usage at mentioned intervals
+ during esim bootstrap activation. If the value set is 0 or -1, default interval of
+ 60000 millis will be set. -->
+ <integer name="config_reevaluate_bootstrap_sim_data_usage_millis">60000</integer>
+ <java-symbol type="integer" name="config_reevaluate_bootstrap_sim_data_usage_millis" />
+
<!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
to identify providers that should be ignored if the carrier config
carrier_supported_satellite_services_per_provider_bundle does not support them.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 461137c..4c941fd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6457,16 +6457,4 @@
<string name="satellite_notification_how_it_works">How it works</string>
<!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
<string name="unarchival_session_app_label">Pending...</string>
-
- <!-- Background user sound notification related messages -->
- <!-- Notification title when sound comes from a call on background user [CHAR LIMIT=NOTIF_TITLE]-->
- <string name="bg_user_sound_notification_title_call">Call for <xliff:g id="user_name" example="John Doe">%s</xliff:g></string>
- <!-- Notification title when sound comes from an alarm or timer on background user [CHAR LIMIT=NOTIF_TITLE]-->
- <string name="bg_user_sound_notification_title_alarm">Alarm for <xliff:g id="user_name" example="John Doe">%s</xliff:g></string>
- <!-- Notification action button to prompt user switch to the background user [CHAR LIMIT=NONE] -->
- <string name="bg_user_sound_notification_button_switch_user">Switch user</string>
- <!-- Notification action button to mute the sound from the background user [CHAR LIMIT=NONE] -->
- <string name="bg_user_sound_notification_button_mute">Mute</string>
- <!-- Notification content action to mute the sound from the background user [CHAR LIMIT=NOTIF_BODY]-->
- <string name="bg_user_sound_notification_message">Tap to mute sound</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 91d7ce0..ead5827 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" />
@@ -5380,13 +5381,6 @@
<java-symbol type="string" name="satellite_notification_how_it_works" />
<java-symbol type="drawable" name="ic_satellite_alt_24px" />
- <!-- System notification for background user sound -->
- <java-symbol type="string" name="bg_user_sound_notification_title_call" />
- <java-symbol type="string" name="bg_user_sound_notification_title_alarm" />
- <java-symbol type="string" name="bg_user_sound_notification_button_switch_user" />
- <java-symbol type="string" name="bg_user_sound_notification_button_mute" />
- <java-symbol type="string" name="bg_user_sound_notification_message" />
-
<!-- DisplayManager configs. -->
<java-symbol type="bool" name="config_evenDimmerEnabled" />
diff --git a/core/tests/FileSystemUtilsTest/TEST_MAPPING b/core/tests/FileSystemUtilsTest/TEST_MAPPING
index 89b3a7a..d41e981 100644
--- a/core/tests/FileSystemUtilsTest/TEST_MAPPING
+++ b/core/tests/FileSystemUtilsTest/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "FileSystemUtilsTests"
}
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d930e4d..3c042ba 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -78,6 +78,6 @@
public void testRestrictionLevel() throws Exception {
// For the moment mostly want to confirm we don't crash
assertNotNull(ActivityManager.restrictionLevelToName(
- ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+ ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED));
}
}
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 3a872b5..5bf88da 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.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 @@
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 @@
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 ce7d6a9..c3ea7d3 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.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 @@
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 @@
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 80fef6c..ccebd03 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1258,18 +1258,6 @@
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 82251b8..4921e4a 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_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 @@
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 eebd133..77b8663 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 @@
*/
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 0c99aed..e7d9812 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.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 @@
});
});
}
+
+ @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 24cf370..c625b69 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.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.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 @@
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 @@
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 @@
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 @@
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/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index 2573931..36d8fba 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -52,6 +52,8 @@
virtual void destroy() {}
+ int getBuffersDataSpace() { return 0; }
+
protected:
virtual ~Surface() {}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7439fbc..eceba92 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -93,6 +93,7 @@
host: {
include_dirs: [
"external/vulkan-headers/include",
+ "frameworks/av/media/ndk/include",
],
cflags: [
"-Wno-unused-variable",
@@ -142,7 +143,6 @@
"libsync",
"libui",
"aconfig_text_flags_c_lib",
- "server_configurable_flags",
],
static_libs: [
"libEGL_blobCache",
@@ -267,6 +267,7 @@
cppflags: ["-Wno-conversion-null"],
srcs: [
+ "apex/android_canvas.cpp",
"apex/android_matrix.cpp",
"apex/android_paint.cpp",
"apex/android_region.cpp",
@@ -279,7 +280,6 @@
android: {
srcs: [ // sources that depend on android only libraries
"apex/android_bitmap.cpp",
- "apex/android_canvas.cpp",
"apex/jni_runtime.cpp",
],
},
@@ -338,6 +338,8 @@
"jni/android_graphics_ColorSpace.cpp",
"jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
"jni/android_graphics_drawable_VectorDrawable.cpp",
+ "jni/android_graphics_HardwareRenderer.cpp",
+ "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/android_graphics_HardwareRendererObserver.cpp",
"jni/android_graphics_Matrix.cpp",
"jni/android_graphics_Picture.cpp",
@@ -422,8 +424,6 @@
android: {
srcs: [ // sources that depend on android only libraries
"jni/android_graphics_TextureLayer.cpp",
- "jni/android_graphics_HardwareRenderer.cpp",
- "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
"jni/Movie.cpp",
@@ -448,6 +448,12 @@
"libstatssocket_lazy",
],
},
+ linux: {
+ srcs: ["platform/linux/utils/SharedLib.cpp"],
+ },
+ darwin: {
+ srcs: ["platform/darwin/utils/SharedLib.cpp"],
+ },
host: {
cflags: [
"-Wno-unused-const-variable",
@@ -543,6 +549,7 @@
"renderthread/CanvasContext.cpp",
"renderthread/DrawFrameTask.cpp",
"renderthread/Frame.cpp",
+ "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/RenderProxy.cpp",
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
@@ -576,6 +583,7 @@
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
+ "Layer.cpp",
"LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
@@ -624,7 +632,6 @@
"renderthread/CacheManager.cpp",
"renderthread/EglManager.cpp",
"renderthread/ReliableSurface.cpp",
- "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/VulkanManager.cpp",
"renderthread/VulkanSurface.cpp",
"renderthread/RenderThread.cpp",
@@ -635,7 +642,6 @@
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
- "Layer.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"WebViewFunctorManager.cpp",
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
index 7e3f771..d3b48d3 100644
--- a/libs/hwui/jni/HardwareBufferHelpers.cpp
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -16,7 +16,9 @@
#include "HardwareBufferHelpers.h"
+#ifdef __ANDROID__
#include <dlfcn.h>
+#endif
#include <log/log.h>
#ifdef __ANDROID__
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d9e2c8c..df9f830 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -25,13 +25,16 @@
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
+#ifdef __ANDROID__
#include <SkImageAndroid.h>
+#else
+#include <SkImagePriv.h>
+#endif
#include <SkPicture.h>
#include <SkPixmap.h>
#include <SkSerialProcs.h>
#include <SkStream.h>
#include <SkTypeface.h>
-#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
@@ -39,8 +42,10 @@
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include <nativehelper/JNIPlatformHelp.h>
+#ifdef __ANDROID__
#include <pipeline/skia/ShaderCache.h>
#include <private/EGL/cache.h>
+#endif
#include <renderthread/CanvasContext.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
@@ -59,6 +64,7 @@
#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
#include "utils/ForceDark.h"
+#include "utils/SharedLib.h"
namespace android {
@@ -498,7 +504,11 @@
return sk_ref_sp(img);
}
bm.setImmutable();
+#ifdef __ANDROID__
return SkImages::PinnableRasterFromBitmap(bm);
+#else
+ return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+#endif
}
return sk_ref_sp(img);
}
@@ -713,6 +723,7 @@
static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env,
jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) {
+#ifdef __ANDROID__
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
if (jwidth <= 0 || jheight <= 0) {
ALOGW("Invalid width %d or height %d", jwidth, jheight);
@@ -796,6 +807,9 @@
sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
return bitmap::createBitmap(env, bitmap.release(),
android::bitmap::kBitmapCreateFlag_Premultiplied);
+#else
+ return nullptr;
+#endif
}
static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
@@ -909,6 +923,7 @@
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath, jstring skiaDiskCachePath) {
+#ifdef __ANDROID__
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
android::egl_set_cache_filename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -916,6 +931,7 @@
const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL);
uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray);
env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray);
+#endif
}
static jboolean android_view_ThreadedRenderer_isWebViewOverlaysEnabled(JNIEnv* env, jobject clazz) {
@@ -1092,8 +1108,12 @@
gCopyRequest.getDestinationBitmap =
GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+#ifdef __ANDROID__
+ void* handle_ = SharedLib::openSharedLib("libandroid");
+#else
+ void* handle_ = SharedLib::openSharedLib("libandroid_runtime");
+#endif
+ fromSurface = (ANW_fromSurface)SharedLib::getSymbol(handle_, "ANativeWindow_fromSurface");
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
"Failed to find required symbol ANativeWindow_fromSurface!");
diff --git a/libs/hwui/platform/darwin/utils/SharedLib.cpp b/libs/hwui/platform/darwin/utils/SharedLib.cpp
new file mode 100644
index 0000000..6e9f0b4
--- /dev/null
+++ b/libs/hwui/platform/darwin/utils/SharedLib.cpp
@@ -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.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".dylib").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/platform/linux/utils/SharedLib.cpp b/libs/hwui/platform/linux/utils/SharedLib.cpp
new file mode 100644
index 0000000..a9acf37
--- /dev/null
+++ b/libs/hwui/platform/linux/utils/SharedLib.cpp
@@ -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.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".so").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/SharedLib.h b/libs/hwui/utils/SharedLib.h
new file mode 100644
index 0000000..f4dcf0f
--- /dev/null
+++ b/libs/hwui/utils/SharedLib.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef SHAREDLIB_H
+#define SHAREDLIB_H
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+class SharedLib {
+public:
+ static void* openSharedLib(std::string filename);
+ static void* getSymbol(void* library, const char* symbol);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // SHAREDLIB_H
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 19e59a7..d6d4989 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -79,6 +79,13 @@
}
flag {
+ name: "subscriptions_listener_thread"
+ namespace: "location"
+ description: "Flag for running onSubscriptionsChangeListener on FgThread"
+ bug: "332451908"
+}
+
+flag {
name: "gnss_configuration_from_resource"
namespace: "location"
description: "Flag for GNSS configuration from resource"
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index ce7474c..3ba0d59 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5232,6 +5232,13 @@
setParameters(keys, values);
}
+ private void logAndRun(String message, Runnable r) {
+ final String TAG = "MediaCodec";
+ android.util.Log.d(TAG, "enter: " + message);
+ r.run();
+ android.util.Log.d(TAG, "exit : " + message);
+ }
+
/**
* Sets an asynchronous callback for actionable MediaCodec events.
*
@@ -5261,14 +5268,40 @@
// even if we were to extend this to be callable dynamically, it must
// be called when codec is flushed, so no messages are pending.
if (newHandler != mCallbackHandler) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[new handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[new handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
mCallbackHandler = newHandler;
}
}
} else if (mCallbackHandler != null) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[null handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[null handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
}
if (mCallbackHandler != null) {
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 5331046..6593533 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -205,6 +205,10 @@
* 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 @@
* 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 @@
* 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 @@
* 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/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 45b4370..212e390 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -528,8 +528,8 @@
@Override
// Declare the receivers associated with your input ports.
- public List<MidiReceiver> onGetInputPortReceivers() {
- return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
+ public List<MidiReceiver> onGetInputPortReceivers() {
+ return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
}
/**
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 94fce79..8609c4d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -82,6 +82,7 @@
"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 @@
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 1c25080..48cd53d 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 @@
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 @@
}
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 @@
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 a64e3f2..36cba2d 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/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 65bbb6fc..a0ebbfe 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -197,7 +197,12 @@
filter(allFilters, BluetoothLeDeviceFilter.class);
final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
- checkBoundDevicesIfNeeded(request, btFilters);
+ // No need to startDiscovery if the device is already bound or connected for
+ // singleDevice dialog.
+ if (checkBoundDevicesIfNeeded(request, btFilters)) {
+ stopSelf();
+ return;
+ }
// If no filters are specified: look for everything.
final boolean forceStartScanningAll = isEmpty(allFilters);
@@ -257,33 +262,37 @@
stopSelf();
}
- private void checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
+ private boolean checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
@NonNull List<BluetoothDeviceFilter> btFilters) {
// If filtering to get single device by mac address, also search in the set of already
// bonded devices to allow linking those directly
- if (btFilters.isEmpty() || !request.isSingleDevice()) return;
+ if (btFilters.isEmpty() || !request.isSingleDevice()) return false;
final BluetoothDeviceFilter singleMacAddressFilter =
find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
- if (singleMacAddressFilter == null) return;
+ if (singleMacAddressFilter == null) return false;
- findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters);
- findAndReportMatches(mBtManager.getConnectedDevices(BluetoothProfile.GATT), btFilters);
- findAndReportMatches(
- mBtManager.getConnectedDevices(BluetoothProfile.GATT_SERVER), btFilters);
+ return findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT_SERVER), btFilters);
}
- private void findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
+ private boolean findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
@NonNull List<BluetoothDeviceFilter> filters) {
- if (devices == null) return;
+ if (devices == null) return false;
for (BluetoothDevice device : devices) {
final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
if (match != null) {
onDeviceFound(match);
+ return true;
}
}
+
+ return false;
}
private BluetoothBroadcastReceiver startBtScanningIfNeeded(
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 fa4d6af..0000000
--- 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 afcf689..0000000
--- 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 e4923bf..0000000
--- 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 fdb15e2..0000000
--- 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 5cdc253..0000000
--- 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 dbbef61..0000000
--- 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/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index e998fe8..2aff2c3 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_alignParentStart="true"
- android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:paddingStart="@dimen/autofill_view_left_padding"
app:tint="?androidprv:attr/materialColorOnSurface"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 9db681a..46a5138 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -121,16 +121,10 @@
<string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the get flow. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="beckett-bakery@gmail.com">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_sign_in_for">Use your account for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_passkey">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved passkey for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
- <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for password authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_password">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved password for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
- <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for saved sign-in authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_saved_sign_in">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved sign-in info for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+ <string name="get_dialog_description_single_tap">Use your screen lock to sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] -->
<string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 429bdbf..7bc3241 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -317,6 +317,14 @@
)
}
+ 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/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index 6bf803a..0d19a45 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -34,7 +34,6 @@
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
import com.android.credentialmanager.model.BiometricRequestInfo
-import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -210,7 +209,7 @@
onCancelFlowAndFinish: () -> Unit
) {
try {
- if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) {
+ if (!canCallBiometricPrompt(biometricDisplayInfo, context)) {
onBiometricFailureFallback(biometricFlowType)
return
}
@@ -250,40 +249,40 @@
* 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
- }
-
- val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials(
- allowedAuthenticators)
-
- 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.
return false
}
val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
- if (allowedAuthContainsDeviceCredential &&
- biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
- BiometricManager.BIOMETRIC_SUCCESS &&
- biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+ if (biometricManager.canAuthenticate(allowedAuthenticators) !=
BiometricManager.BIOMETRIC_SUCCESS) {
- return true
+ return false
}
+ if (ifOnlySupportsAtMostDeviceCredentials(biometricManager)) return false
+
+ 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
+ ) {
+ return true
+ }
return false
}
@@ -480,16 +479,7 @@
)
descriptionText = context.getString(
- when (singleEntryType) {
- CredentialType.PASSKEY ->
- R.string.get_dialog_description_single_tap_passkey
-
- CredentialType.PASSWORD ->
- R.string.get_dialog_description_single_tap_password
-
- CredentialType.UNKNOWN ->
- R.string.get_dialog_description_single_tap_saved_sign_in
- },
+ R.string.get_dialog_description_single_tap,
getRequestDisplayInfo.appName,
username
)
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 149c14a..2c3c63b 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.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.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.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 MoreOptionTopAppBar(
text: String,
onNavigationIconClicked: () -> Unit,
+ navigationIcon: ImageVector,
+ navigationIconContentDescription: String,
bottomPadding: Dp,
) {
Row(
@@ -336,40 +335,6 @@
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 a0915d2..282a1b5 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.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 @@
onCancelFlowAndFinish = viewModel::onUserCancel,
onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
onMoreOptionSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ viewModel::createFlowOnMoreOptionsOnlySelectedOnCreationSelection,
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderInfo = createCredentialUiState
.activeEntry?.activeProvider!!,
@@ -120,6 +122,41 @@
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 @@
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 ddd4139..130937c 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 @@
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 ce4f402..c98bb5e 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.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.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 @@
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 @@
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/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,6 +46,7 @@
sdk_version: "system_current",
rename_resources_package: false,
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
@@ -78,6 +79,7 @@
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-livedata",
@@ -110,6 +112,7 @@
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 05f4d69..bf69d3b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,6 +146,17 @@
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
android:exported="false" />
+ <!-- Wearable Components -->
+ <service android:name=".wear.WearPackageInstallerService"
+ android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
+ android:foregroundServiceType="systemExempted"
+ android:exported="true"/>
+
+ <provider android:name=".wear.WearPackageIconProvider"
+ android:authorities="com.google.android.packageinstaller.wear.provider"
+ android:grantUriPermissions="true"
+ android:exported="false" />
+
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
new file mode 100644
index 0000000..53a460d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Task that installs an APK. This must not be called on the main thread.
+ * This code is based off the Finsky/Wearsky implementation
+ */
+public class InstallTask {
+ private static final String TAG = "InstallTask";
+
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ private final Context mContext;
+ private String mPackageName;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private PackageInstallerImpl.InstallListener mCallback;
+ private PackageInstaller.Session mSession;
+ private IntentSender mCommitCallback;
+
+ private Exception mException = null;
+ private int mErrorCode = 0;
+ private String mErrorDesc = null;
+
+ public InstallTask(Context context, String packageName,
+ ParcelFileDescriptor parcelFileDescriptor,
+ PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
+ IntentSender commitCallback) {
+ mContext = context;
+ mPackageName = packageName;
+ mParcelFileDescriptor = parcelFileDescriptor;
+ mCallback = callback;
+ mSession = session;
+ mCommitCallback = commitCallback;
+ }
+
+ public boolean isError() {
+ return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
+ }
+
+ public void execute() {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("This method cannot be called from the UI thread.");
+ }
+
+ OutputStream sessionStream = null;
+ try {
+ sessionStream = mSession.openWrite(mPackageName, 0, -1);
+
+ // 2b: Stream the asset to the installer. Note:
+ // Note: writeToOutputStreamFromAsset() always safely closes the input stream
+ writeToOutputStreamFromAsset(sessionStream);
+ mSession.fsync(sessionStream);
+ } catch (Exception e) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
+ mErrorDesc = "Could not write to stream";
+ } finally {
+ if (sessionStream != null) {
+ // 2c: close output stream
+ try {
+ sessionStream.close();
+ } catch (Exception e) {
+ // Ignore otherwise
+ if (mException == null) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
+ mErrorDesc = "Could not close session stream";
+ }
+ }
+ }
+ }
+
+ if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
+ // An error occurred, we're done
+ Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
+ + mErrorDesc + ", " + mException);
+ mSession.close();
+ mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
+ } else {
+ // 3. Commit the session (this actually installs it.) Session map
+ // will be cleaned up in the callback.
+ mCallback.installBeginning();
+ mSession.commit(mCommitCallback);
+ mSession.close();
+ }
+ }
+
+ /**
+ * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
+ * corresponding to the {@code Asset} and then write the contents into an
+ * {@code OutputStream} that is passed in.
+ * <br>
+ * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
+ */
+ private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
+ if (outputStream == null) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
+ mErrorDesc = "Got a null OutputStream.";
+ return false;
+ }
+
+ if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) {
+ mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
+ mErrorDesc = "Could not get FD";
+ return false;
+ }
+
+ InputStream inputStream = null;
+ try {
+ byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
+ int bytesRead;
+ inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
+
+ while ((bytesRead = inputStream.read(inputBuf)) > -1) {
+ if (bytesRead > 0) {
+ outputStream.write(inputBuf, 0, bytesRead);
+ }
+ }
+
+ outputStream.flush();
+ } catch (IOException e) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
+ mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
+ return false;
+ } finally {
+ safeClose(inputStream);
+ }
+
+ return true;
+ }
+
+ /**
+ * Quietly close a closeable resource (e.g. a stream or file). The input may already
+ * be closed and it may even be null.
+ */
+ public static void safeClose(Closeable resource) {
+ if (resource != null) {
+ try {
+ resource.close();
+ } catch (IOException ioe) {
+ // Catch and discard the error
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
new file mode 100644
index 0000000..3daf3d8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+/**
+ * Constants for Installation / Uninstallation requests.
+ * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
+ */
+public class InstallerConstants {
+ /** Request succeeded */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * The new PackageInstaller also returns a small set of less granular error codes, which
+ * we'll remap to the range -500 and below to keep away from existing installer codes
+ * (which run from -1 to -110).
+ */
+ public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
+
+ public static final int ERROR_COULD_NOT_GET_FD = -603;
+ /** This node is not targeted by this request. */
+
+ /** The install did not complete because could not create PackageInstaller session */
+ public final static int ERROR_INSTALL_CREATE_SESSION = -612;
+ /** The install did not complete because could not open PackageInstaller session */
+ public final static int ERROR_INSTALL_OPEN_SESSION = -613;
+ /** The install did not complete because could not open PackageInstaller output stream */
+ public final static int ERROR_INSTALL_OPEN_STREAM = -614;
+ /** The install did not complete because of an exception while streaming bytes */
+ public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
+ /** The install did not complete because of an unexpected exception from PackageInstaller */
+ public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
+ /** The install did not complete because of an unexpected userActionRequired callback */
+ public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
+ /** The install did not complete because of an unexpected broadcast (missing fields) */
+ public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
+ /** The install did not complete because of an error while copying from downloaded file */
+ public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
+ /** The install did not complete because of an error while copying to the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_COPY_STREAM = -620;
+ /** The install did not complete because of an error while closing the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
new file mode 100644
index 0000000..bdc22cf
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+import android.content.Context;
+
+/**
+ * Factory that creates a Package Installer.
+ */
+public class PackageInstallerFactory {
+ private static PackageInstallerImpl sPackageInstaller;
+
+ /**
+ * Return the PackageInstaller shared object. {@code init} should have already been called.
+ */
+ public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
+ if (sPackageInstaller == null) {
+ sPackageInstaller = new PackageInstallerImpl(context);
+ }
+ return sPackageInstaller;
+ }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
new file mode 100644
index 0000000..1e37f15
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of package manager installation using modern PackageInstaller api.
+ *
+ * Heavily copied from Wearsky/Finsky implementation
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class PackageInstallerImpl {
+ private static final String TAG = "PackageInstallerImpl";
+
+ /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
+ private static final String ACTION_INSTALL_COMMIT =
+ "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
+
+ private final Context mContext;
+ private final PackageInstaller mPackageInstaller;
+ private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
+ private final Map<String, PackageInstaller.Session> mOpenSessionMap;
+
+ public PackageInstallerImpl(Context context) {
+ mContext = context.getApplicationContext();
+ mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+
+ // Capture a map of known sessions
+ // This list will be pruned a bit later (stale sessions will be canceled)
+ mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
+ List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
+ for (int i = 0; i < mySessions.size(); i++) {
+ PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
+ String packageName = sessionInfo.getAppPackageName();
+ PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
+
+ // Checking for old info is strictly for logging purposes
+ if (oldInfo != null) {
+ Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
+ .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
+ }
+ }
+ mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
+ }
+
+ /**
+ * This callback will be made after an installation attempt succeeds or fails.
+ */
+ public interface InstallListener {
+ /**
+ * This callback signals that preflight checks have succeeded and installation
+ * is beginning.
+ */
+ void installBeginning();
+
+ /**
+ * This callback signals that installation has completed.
+ */
+ void installSucceeded();
+
+ /**
+ * This callback signals that installation has failed.
+ */
+ void installFailed(int errorCode, String errorDesc);
+ }
+
+ /**
+ * This is a placeholder implementation that bundles an entire "session" into a single
+ * call. This will be replaced by more granular versions that allow longer session lifetimes,
+ * download progress tracking, etc.
+ *
+ * This must not be called on main thread.
+ */
+ public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
+ final InstallListener callback) {
+ // 0. Generic try/catch block because I am not really sure what exceptions (other than
+ // IOException) might be thrown by PackageInstaller and I want to handle them
+ // at least slightly gracefully.
+ try {
+ // 1. Create or recover a session, and open it
+ // Try recovery first
+ PackageInstaller.Session session = null;
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo != null) {
+ // See if it's openable, or already held open
+ session = getSession(packageName);
+ }
+ // If open failed, or there was no session, create a new one and open it.
+ // If we cannot create or open here, the failure is terminal.
+ if (session == null) {
+ try {
+ innerCreateSession(packageName);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
+ "Could not create session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ sessionInfo = mSessionInfoMap.get(packageName);
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ mOpenSessionMap.put(packageName, session);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
+ "Can't open session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ }
+
+ // 2. Launch task to handle file operations.
+ InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
+ callback, session,
+ getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
+ task.execute();
+ if (task.isError()) {
+ cancelSession(sessionInfo.getSessionId(), packageName);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
+ + e.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
+ "Unexpected exception while installing " + packageName);
+ }
+ }
+
+ /**
+ * Retrieve an existing session. Will open if needed, but does not attempt to create.
+ */
+ private PackageInstaller.Session getSession(String packageName) {
+ // Check for already-open session
+ PackageInstaller.Session session = mOpenSessionMap.get(packageName);
+ if (session != null) {
+ try {
+ // Probe the session to ensure that it's still open. This may or may not
+ // throw (if non-open), but it may serve as a canary for stale sessions.
+ session.getNames();
+ return session;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
+ mOpenSessionMap.remove(packageName);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
+ mOpenSessionMap.remove(packageName);
+ }
+ }
+ // Check to see if this is a known session
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo == null) {
+ return null;
+ }
+ // Try to open it. If we fail here, assume that the SessionInfo was stale.
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ } catch (SecurityException se) {
+ Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ } catch (IOException ioe) {
+ Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
+ + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ }
+ mOpenSessionMap.put(packageName, session);
+ return session;
+ }
+
+ /** This version throws an IOException when the session cannot be created */
+ private void innerCreateSession(String packageName) throws IOException {
+ if (mSessionInfoMap.containsKey(packageName)) {
+ Log.w(TAG, "Creating session for " + packageName + " when one already exists");
+ return;
+ }
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(packageName);
+
+ // IOException may be thrown at this point
+ int sessionId = mPackageInstaller.createSession(params);
+ PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
+ mSessionInfoMap.put(packageName, sessionInfo);
+ }
+
+ /**
+ * Cancel a session based on its sessionId. Package name is for logging only.
+ */
+ private void cancelSession(int sessionId, String packageName) {
+ // Close if currently held open
+ closeSession(packageName);
+ // Remove local record
+ mSessionInfoMap.remove(packageName);
+ try {
+ mPackageInstaller.abandonSession(sessionId);
+ } catch (SecurityException se) {
+ // The session no longer exists, so we can exit quietly.
+ return;
+ }
+ }
+
+ /**
+ * Close a session if it happens to be held open.
+ */
+ private void closeSession(String packageName) {
+ PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
+ if (session != null) {
+ // Unfortunately close() is not idempotent. Try our best to make this safe.
+ try {
+ session.close();
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
+ + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Creates a commit callback for the package install that's underway. This will be called
+ * some time after calling session.commit() (above).
+ */
+ private IntentSender getCommitCallback(final String packageName, final int sessionId,
+ final InstallListener callback) {
+ // Create a single-use broadcast receiver
+ BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mContext.unregisterReceiver(this);
+ handleCommitCallback(intent, packageName, sessionId, callback);
+ }
+ };
+ // Create a matching intent-filter and register the receiver
+ String action = ACTION_INSTALL_COMMIT + "." + packageName;
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(action);
+ mContext.registerReceiver(broadcastReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED);
+
+ // Create a matching PendingIntent and use it to generate the IntentSender
+ Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
+ broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+ return pendingIntent.getIntentSender();
+ }
+
+ /**
+ * Examine the extras to determine information about the package update/install, decode
+ * the result, and call the appropriate callback.
+ *
+ * @param intent The intent, which the PackageInstaller will have added Extras to
+ * @param packageName The package name we created the receiver for
+ * @param sessionId The session Id we created the receiver for
+ * @param callback The callback to report success/failure to
+ */
+ private void handleCommitCallback(Intent intent, String packageName, int sessionId,
+ InstallListener callback) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installation of " + packageName + " finished with extras "
+ + intent.getExtras());
+ }
+ String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+ int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ cancelSession(sessionId, packageName);
+ callback.installSucceeded();
+ } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
+ // TODO - use the constant when the correct/final name is in the SDK
+ // TODO This is unexpected, so we are treating as failure for now
+ cancelSession(sessionId, packageName);
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
+ "Unexpected: user action required");
+ } else {
+ cancelSession(sessionId, packageName);
+ int errorCode = getPackageManagerErrorCode(status);
+ Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
+ + statusMessage);
+ callback.installFailed(errorCode, null);
+ }
+ }
+
+ private int getPackageManagerErrorCode(int status) {
+ // This is a hack: because PackageInstaller now reports error codes
+ // with small positive values, we need to remap them into a space
+ // that is more compatible with the existing package manager error codes.
+ // See https://sites.google.com/a/google.com/universal-store/documentation
+ // /android-client/download-error-codes
+ int errorCode;
+ if (status == Integer.MIN_VALUE) {
+ errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
+ } else {
+ errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
+ }
+ return errorCode;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
new file mode 100644
index 0000000..2c289b2
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Installation Util that contains a list of parameters that are needed for
+ * installing/uninstalling.
+ */
+public class WearPackageArgs {
+ private static final String KEY_PACKAGE_NAME =
+ "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+ private static final String KEY_ASSET_URI =
+ "com.google.android.clockwork.EXTRA_ASSET_URI";
+ private static final String KEY_START_ID =
+ "com.google.android.clockwork.EXTRA_START_ID";
+ private static final String KEY_PERM_URI =
+ "com.google.android.clockwork.EXTRA_PERM_URI";
+ private static final String KEY_CHECK_PERMS =
+ "com.google.android.clockwork.EXTRA_CHECK_PERMS";
+ private static final String KEY_SKIP_IF_SAME_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
+ private static final String KEY_COMPRESSION_ALG =
+ "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
+ private static final String KEY_COMPANION_SDK_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
+ private static final String KEY_COMPANION_DEVICE_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
+ private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
+ "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
+ private static final String KEY_SKIP_IF_LOWER_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
+
+ public static String getPackageName(Bundle b) {
+ return b.getString(KEY_PACKAGE_NAME);
+ }
+
+ public static Bundle setPackageName(Bundle b, String packageName) {
+ b.putString(KEY_PACKAGE_NAME, packageName);
+ return b;
+ }
+
+ public static Uri getAssetUri(Bundle b) {
+ return b.getParcelable(KEY_ASSET_URI);
+ }
+
+ public static Uri getPermUri(Bundle b) {
+ return b.getParcelable(KEY_PERM_URI);
+ }
+
+ public static boolean checkPerms(Bundle b) {
+ return b.getBoolean(KEY_CHECK_PERMS);
+ }
+
+ public static boolean skipIfSameVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
+ }
+
+ public static int getCompanionSdkVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_SDK_VERSION);
+ }
+
+ public static int getCompanionDeviceVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_DEVICE_VERSION);
+ }
+
+ public static String getCompressionAlg(Bundle b) {
+ return b.getString(KEY_COMPRESSION_ALG);
+ }
+
+ public static int getStartId(Bundle b) {
+ return b.getInt(KEY_START_ID);
+ }
+
+ public static boolean skipIfLowerVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
+ }
+
+ public static Bundle setStartId(Bundle b, int startId) {
+ b.putInt(KEY_START_ID, startId);
+ return b;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
new file mode 100644
index 0000000..02b9d29
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+public class WearPackageIconProvider extends ContentProvider {
+ private static final String TAG = "WearPackageIconProvider";
+ public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
+
+ private static final String REQUIRED_PERMISSION =
+ "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
+
+ /** MIME types. */
+ public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("Query is not supported.");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ if (AUTHORITY.equals(uri.getEncodedAuthority())) {
+ return ICON_TYPE;
+ }
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert is not supported.");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update is not supported.");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+ }
+ return null;
+ }
+
+ public static Uri getUriForPackage(final String packageName) {
+ return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
+ }
+
+ private String getPackageNameFromUri(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ List<String> pathSegments = uri.getPathSegments();
+ String packageName = pathSegments.get(pathSegments.size() - 1);
+
+ if (packageName.endsWith(".icon")) {
+ packageName = packageName.substring(0, packageName.lastIndexOf("."));
+ }
+ return packageName;
+ }
+
+ /**
+ * Make sure the calling app is either a system app or the same app or has the right permission.
+ * @throws SecurityException if the caller has insufficient permissions.
+ */
+ @TargetApi(Build.VERSION_CODES.BASE_1_1)
+ private void enforcePermissions(Uri uri) {
+ // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
+ // allow System process to access this provider.
+ Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int myUid = android.os.Process.myUid();
+
+ if (uid == myUid || isSystemApp(context, pid)) {
+ return;
+ }
+
+ if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ }
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ throw new SecurityException("Permission Denial: reading "
+ + getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid);
+ }
+
+ /**
+ * From the pid of the calling process, figure out whether this is a system app or not. We do
+ * this by checking the application information corresponding to the pid and then checking if
+ * FLAG_SYSTEM is set.
+ */
+ @TargetApi(Build.VERSION_CODES.CUPCAKE)
+ private boolean isSystemApp(Context context, int pid) {
+ // Get the Activity Manager Object
+ ActivityManager aManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ // Get the list of running Applications
+ List<ActivityManager.RunningAppProcessInfo> rapInfoList =
+ aManager.getRunningAppProcesses();
+ for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
+ if (rapInfo.pid == pid) {
+ try {
+ PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
+ rapInfo.pkgList[0], 0);
+ if (pkgInfo != null && pkgInfo.applicationInfo != null &&
+ (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ Log.d(TAG, pid + " is a system app.");
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not find package information.", e);
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
new file mode 100644
index 0000000..ae0f4ec
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import androidx.annotation.Nullable;
+import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.PackageUtil;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.common.EventResultPersister;
+import com.android.packageinstaller.common.UninstallEventReceiver;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service that will install/uninstall packages. It will check for permissions and features as well.
+ *
+ * -----------
+ *
+ * Debugging information:
+ *
+ * Install Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
+ * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
+ * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
+ * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Uninstall Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Retry GMS:
+ * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ */
+public class WearPackageInstallerService extends Service
+ implements EventResultPersister.EventResultObserver {
+ private static final String TAG = "WearPkgInstallerService";
+
+ private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
+
+ private final int START_INSTALL = 1;
+ private final int START_UNINSTALL = 2;
+
+ private int mInstallNotificationId = 1;
+ private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+ private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+ private class UninstallParams {
+ public String mPackageName;
+ public PowerManager.WakeLock mLock;
+
+ UninstallParams(String packageName, PowerManager.WakeLock lock) {
+ mPackageName = packageName;
+ mLock = lock;
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case START_INSTALL:
+ installPackage(msg.getData());
+ break;
+ case START_UNINSTALL:
+ uninstallPackage(msg.getData());
+ break;
+ }
+ }
+ }
+ private ServiceHandler mServiceHandler;
+ private NotificationChannel mNotificationChannel;
+ private static volatile PowerManager.WakeLock lockStatic = null;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("PackageInstallerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ mServiceHandler = new ServiceHandler(thread.getLooper());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (!DeviceUtils.isWear(this)) {
+ Log.w(TAG, "Not running on wearable.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (intent == null) {
+ Log.w(TAG, "Got null intent.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Got install/uninstall request " + intent);
+ }
+
+ Uri packageUri = intent.getData();
+ if (packageUri == null) {
+ Log.e(TAG, "No package URI in intent");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
+ if (packageName == null) {
+ Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ if (!lock.isHeld()) {
+ lock.acquire();
+ }
+
+ Bundle intentBundle = intent.getExtras();
+ if (intentBundle == null) {
+ intentBundle = new Bundle();
+ }
+ WearPackageArgs.setStartId(intentBundle, startId);
+ WearPackageArgs.setPackageName(intentBundle, packageName);
+ Message msg;
+ String notifTitle;
+ if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_INSTALL);
+ notifTitle = getString(R.string.installing);
+ } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_UNINSTALL);
+ notifTitle = getString(R.string.uninstalling);
+ } else {
+ Log.e(TAG, "Unknown action : " + intent.getAction());
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
+ startForeground(notifPair.first, notifPair.second);
+ msg.setData(intentBundle);
+ mServiceHandler.sendMessage(msg);
+ return START_NOT_STICKY;
+ }
+
+ private void installPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+ final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
+ final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
+ boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
+ boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
+ int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
+ int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
+ String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
+ boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
+ ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
+ checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
+ ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
+ companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
+ ", skipIfLowerVersion: " + skipIfLowerVersion);
+ }
+ final PackageManager pm = getPackageManager();
+ File tempFile = null;
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ boolean messageSent = false;
+ try {
+ PackageInfo existingPkgInfo = null;
+ try {
+ existingPkgInfo = pm.getPackageInfo(packageName,
+ PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
+ if (existingPkgInfo != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Replacing package:" + packageName);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore this exception. We could not find the package, will treat as a new
+ // installation.
+ }
+ // TODO(28021618): This was left as a temp file due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this permission logic might want to be
+ // reworked to use a stream instead of a file so that we don't need to write a
+ // file at all. Note that there might be some trickiness with opening a stream
+ // for multiple users.
+ ParcelFileDescriptor parcelFd = getContentResolver()
+ .openFileDescriptor(assetUri, "r");
+ tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
+ parcelFd, packageName, compressionAlg);
+ if (tempFile == null) {
+ Log.e(TAG, "Could not create a temp file from FD for " + packageName);
+ return;
+ }
+ PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
+ PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
+ if (pkgInfo == null) {
+ Log.e(TAG, "Could not parse apk information for " + packageName);
+ return;
+ }
+
+ if (!pkgInfo.packageName.equals(packageName)) {
+ Log.e(TAG, "Wearable Package Name has to match what is provided for " +
+ packageName);
+ return;
+ }
+
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ appInfo.sourceDir = tempFile.getPath();
+ appInfo.publicSourceDir = tempFile.getPath();
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.installing_app, appInfo.loadLabel(pm)));
+
+ List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
+
+ // Log if the installed pkg has a higher version number.
+ if (existingPkgInfo != null) {
+ long longVersionCode = pkgInfo.getLongVersionCode();
+ if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
+ if (skipIfSameVersion) {
+ Log.w(TAG, "Version number (" + longVersionCode +
+ ") of new app is equal to existing app for " + packageName +
+ "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is equal to existing app for " + packageName);
+ }
+ } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
+ if (skipIfLowerVersion) {
+ // Starting in Feldspar, we are not going to allow downgrades of any app.
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() +
+ ") for " + packageName + "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
+ }
+ }
+
+ // Following the Android Phone model, we should only check for permissions for any
+ // newly defined perms.
+ if (existingPkgInfo.requestedPermissions != null) {
+ for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
+ // If the permission is granted, then we will not ask to request it again.
+ if ((existingPkgInfo.requestedPermissionsFlags[i] &
+ PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
+ " is already granted for " + packageName);
+ }
+ wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
+ }
+ }
+ }
+ }
+
+ // Check that the wearable has all the features.
+ boolean hasAllFeatures = true;
+ for (FeatureInfo feature : pkgInfo.reqFeatures) {
+ if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
+ (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
+ Log.e(TAG, "Wearable does not have required feature: " + feature +
+ " for " + packageName);
+ hasAllFeatures = false;
+ }
+ }
+
+ if (!hasAllFeatures) {
+ return;
+ }
+
+ // Check permissions on both the new wearable package and also on the already installed
+ // wearable package.
+ // If the app is targeting API level 23, we will also start a service in ClockworkHome
+ // which will ultimately prompt the user to accept/reject permissions.
+ if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
+ companionDeviceVersion, permUri, wearablePerms, tempFile)) {
+ Log.w(TAG, "Wearable does not have enough permissions.");
+ return;
+ }
+
+ // Finally install the package.
+ ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
+ PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
+ new PackageInstallListener(this, lock, startId, packageName));
+
+ messageSent = true;
+ Log.i(TAG, "Sent installation request for " + packageName);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Could not find the file with URI " + assetUri, e);
+ } finally {
+ if (!messageSent) {
+ // Some error happened. If the message has been sent, we can wait for the observer
+ // which will finish the service.
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ finishService(lock, startId);
+ }
+ }
+ }
+
+ // TODO: This was left using the old PackageManager API due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this logic should be reworked to use the new
+ // PackageInstaller APIs similar to how installPackage was reworked
+ private void uninstallPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+ UninstallParams params = new UninstallParams(packageName, lock);
+ mServiceIdToParams.put(startId, params);
+
+ final PackageManager pm = getPackageManager();
+ try {
+ PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
+
+ int uninstallId = UninstallEventReceiver.addObserver(this,
+ EventResultPersister.GENERATE_NEW_ID, this);
+
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+ broadcastIntent.setPackage(getPackageName());
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+ broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+
+ // Found package, send uninstall request.
+ pm.getPackageInstaller().uninstall(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ PackageManager.DELETE_ALL_USERS,
+ pendingIntent.getIntentSender());
+
+ Log.i(TAG, "Sent delete request for " + packageName);
+ } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
+ // Couldn't find the package, no need to call uninstall.
+ Log.w(TAG, "Could not find package, not deleting " + packageName, e);
+ finishService(lock, startId);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ Log.e(TAG, "Fails to start uninstall", e);
+ finishService(lock, startId);
+ }
+ }
+
+ @Override
+ public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+ if (mServiceIdToParams.containsKey(serviceId)) {
+ UninstallParams params = mServiceIdToParams.get(serviceId);
+ try {
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+ } else {
+ Log.e(TAG, "Package uninstall failed " + params.mPackageName
+ + ", returnCode " + legacyStatus);
+ }
+ } finally {
+ finishService(params.mLock, serviceId);
+ }
+ }
+ }
+
+ private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
+ int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
+ File apkFile) {
+ // Assumption: We are running on Android O.
+ // If the Phone App is targeting M, all permissions may not have been granted to the phone
+ // app. If the Wear App is then not targeting M, there may be permissions that are not
+ // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
+ // app.
+ if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+ // Install the app if Wear App is ready for the new perms model.
+ return true;
+ }
+
+ if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
+ // All permissions requested by the watch are already granted on the phone, no need
+ // to do anything.
+ return true;
+ }
+
+ // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
+ if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
+ Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
+ + "phone app is targeting at least 23, will continue.");
+ }
+
+ return false;
+ }
+
+ /**
+ * Given a {@string packageName} corresponding to a phone app, query the provider for all the
+ * perms that are granted.
+ *
+ * @return true if the Wear App has any perms that have not been granted yet on the phone side.
+ * @return true if there is any error cases.
+ */
+ private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
+ List<String> wearablePermissions) {
+ if (permUri == null) {
+ Log.e(TAG, "Permission URI is null");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+ Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
+ if (permCursor == null) {
+ Log.e(TAG, "Could not get the cursor for the permissions");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+
+ Set<String> grantedPerms = new HashSet<>();
+ Set<String> ungrantedPerms = new HashSet<>();
+ while(permCursor.moveToNext()) {
+ // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
+ // verify their types.
+ if (permCursor.getColumnCount() == 2
+ && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
+ && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
+ String perm = permCursor.getString(0);
+ Integer granted = permCursor.getInt(1);
+ if (granted == 1) {
+ grantedPerms.add(perm);
+ } else {
+ ungrantedPerms.add(perm);
+ }
+ }
+ }
+ permCursor.close();
+
+ boolean hasUngrantedPerm = false;
+ for (String wearablePerm : wearablePermissions) {
+ if (!grantedPerms.contains(wearablePerm)) {
+ hasUngrantedPerm = true;
+ if (!ungrantedPerms.contains(wearablePerm)) {
+ // This is an error condition. This means that the wearable has permissions that
+ // are not even declared in its host app. This is a developer error.
+ Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
+ + "\" that is not defined in the host application's manifest.");
+ } else {
+ Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
+ "\" that is not granted in the host application.");
+ }
+ }
+ }
+ return hasUngrantedPerm;
+ }
+
+ /** Finishes the service after fulfilling obligation to call startForeground. */
+ private void finishServiceEarly(int startId) {
+ Pair<Integer, Notification> notifPair = buildNotification(
+ getApplicationContext().getPackageName(), "");
+ startForeground(notifPair.first, notifPair.second);
+ finishService(null, startId);
+ }
+
+ private void finishService(PowerManager.WakeLock lock, int startId) {
+ if (lock != null && lock.isHeld()) {
+ lock.release();
+ }
+ stopSelf(startId);
+ }
+
+ private synchronized PowerManager.WakeLock getLock(Context context) {
+ if (lockStatic == null) {
+ PowerManager mgr =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ lockStatic = mgr.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
+ lockStatic.setReferenceCounted(true);
+ }
+ return lockStatic;
+ }
+
+ private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
+ private Context mContext;
+ private PowerManager.WakeLock mWakeLock;
+ private int mStartId;
+ private String mApplicationPackageName;
+ private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
+ int startId, String applicationPackageName) {
+ mContext = context;
+ mWakeLock = wakeLock;
+ mStartId = startId;
+ mApplicationPackageName = applicationPackageName;
+ }
+
+ @Override
+ public void installBeginning() {
+ Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
+ }
+
+ @Override
+ public void installSucceeded() {
+ try {
+ Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
+
+ // Delete tempFile from the file system.
+ File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ } finally {
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ @Override
+ public void installFailed(int errorCode, String errorDesc) {
+ Log.e(TAG, "Package install failed " + mApplicationPackageName
+ + ", errorCode " + errorCode);
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
+ final String title) {
+ int notifId;
+ if (mNotifIdMap.containsKey(packageName)) {
+ notifId = mNotifIdMap.get(packageName);
+ } else {
+ notifId = mInstallNotificationId++;
+ mNotifIdMap.put(packageName, notifId);
+ }
+
+ if (mNotificationChannel == null) {
+ mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
+ getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(mNotificationChannel);
+ }
+ return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
+ .setSmallIcon(R.drawable.ic_file_download)
+ .setContentTitle(title)
+ .build());
+ }
+
+ private void getLabelAndUpdateNotification(String packageName, String title) {
+ // Update notification since we have a label now.
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
+ notificationManager.notify(notifPair.first, notifPair.second);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
new file mode 100644
index 0000000..6a9145d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.tukaani.xz.LZMAInputStream;
+import org.tukaani.xz.XZInputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WearPackageUtil {
+ private static final String TAG = "WearablePkgInstaller";
+
+ private static final String COMPRESSION_LZMA = "lzma";
+ private static final String COMPRESSION_XZ = "xz";
+
+ public static File getTemporaryFile(Context context, String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "tmp");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ File newFile = new File(newFileDir, packageName + ".apk");
+ return newFile;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ public static File getIconFile(final Context context, final String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "images/icons");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ return new File(newFileDir, packageName + ".icon");
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ /**
+ * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
+ * by the PackageManager, we will parse it before sending it to the PackageManager.
+ * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
+ * the fd to a File.
+ *
+ * @param context
+ * @param fd FileDescriptor to convert to File
+ * @param packageName Name of package, will define the name of the file
+ * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
+ * decompress it here
+ */
+ public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
+ String packageName, String compressionAlg) {
+ File newFile = getTemporaryFile(context, packageName);
+ if (fd == null || fd.getFileDescriptor() == null) {
+ return null;
+ }
+ InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ try {
+ if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
+ fr = new XZInputStream(fr);
+ } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
+ fr = new LZMAInputStream(fr);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
+ return null;
+ }
+
+ int nRead;
+ byte[] data = new byte[1024];
+ try {
+ final FileOutputStream fo = new FileOutputStream(newFile);
+ while ((nRead = fr.read(data, 0, data.length)) != -1) {
+ fo.write(data, 0, nRead);
+ }
+ fo.flush();
+ fo.close();
+ Os.chmod(newFile.getAbsolutePath(), 0644);
+ return newFile;
+ } catch (IOException e) {
+ Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
+ return null;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Could not set permissions on file ", e);
+ return null;
+ } finally {
+ try {
+ fr.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close the file from FD ", e);
+ }
+ }
+ }
+
+ /**
+ * @return com.google.com from expected formats like
+ * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
+ */
+ public static String getSanitizedPackageName(Uri packageUri) {
+ String packageName = packageUri.getEncodedSchemeSpecificPart();
+ if (packageName != null) {
+ return packageName.replaceAll("^/+", "");
+ }
+ return packageName;
+ }
+}
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 0000000..fadcf7b
--- /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 0000000..0c20287
--- /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 0000000..7c9d1a4
--- /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/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
deleted file mode 100644
index c7fbb5f..0000000
--- a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
+++ /dev/null
@@ -1,26 +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.
- -->
-
-<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>
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 a2b9648..0000000
--- 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/Graph/Android.bp b/packages/SettingsLib/Graph/Android.bp
new file mode 100644
index 0000000..e2ed1e4
--- /dev/null
+++ b/packages/SettingsLib/Graph/Android.bp
@@ -0,0 +1,21 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "SettingsLibGraph-srcs",
+ srcs: ["src/**/*"],
+}
+
+android_library {
+ name: "SettingsLibGraph",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+ srcs: [":SettingsLibGraph-srcs"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Graph/AndroidManifest.xml b/packages/SettingsLib/Graph/AndroidManifest.xml
new file mode 100644
index 0000000..93acb35
--- /dev/null
+++ b/packages/SettingsLib/Graph/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.graph">
+
+ <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
new file mode 100644
index 0000000..9231f40
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
@@ -0,0 +1,70 @@
+package com.android.settingslib.graph
+
+import androidx.annotation.StringRes
+import androidx.annotation.XmlRes
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceScreen
+
+/** Manager to create and initialize preference screen. */
+class PreferenceScreenManager(private val preferenceManager: PreferenceManager) {
+ private val context = preferenceManager.context
+ // the map will preserve order
+ private val updaters = mutableMapOf<String, PreferenceUpdater>()
+ private val screenUpdaters = mutableListOf<PreferenceScreenUpdater>()
+
+ /** Creates an empty [PreferenceScreen]. */
+ fun createPreferenceScreen(): PreferenceScreen =
+ preferenceManager.createPreferenceScreen(context)
+
+ /** Creates [PreferenceScreen] from resource. */
+ fun createPreferenceScreen(@XmlRes xmlRes: Int): PreferenceScreen =
+ preferenceManager.inflateFromResource(context, xmlRes, null)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(@StringRes key: Int, updater: PreferenceUpdater) =
+ addPreferenceUpdater(context.getString(key), updater)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(
+ key: String,
+ updater: PreferenceUpdater,
+ ): PreferenceScreenManager {
+ updaters.put(key, updater)?.let { if (it != updater) throw IllegalArgumentException() }
+ return this
+ }
+
+ /** Adds updater for preference screen. */
+ fun addPreferenceScreenUpdater(updater: PreferenceScreenUpdater): PreferenceScreenManager {
+ screenUpdaters.add(updater)
+ return this
+ }
+
+ /** Adds a list of updaters for preference screen. */
+ fun addPreferenceScreenUpdater(
+ vararg updaters: PreferenceScreenUpdater,
+ ): PreferenceScreenManager {
+ screenUpdaters.addAll(updaters)
+ return this
+ }
+
+ /** Updates preference screen with registered updaters. */
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen) {
+ for ((key, updater) in updaters) {
+ preferenceScreen.findPreference<Preference>(key)?.let { updater.updatePreference(it) }
+ }
+ for (updater in screenUpdaters) {
+ updater.updatePreferenceScreen(preferenceScreen)
+ }
+ }
+}
+
+/** Updater of [Preference]. */
+interface PreferenceUpdater {
+ fun updatePreference(preference: Preference)
+}
+
+/** Updater of [PreferenceScreen]. */
+interface PreferenceScreenUpdater {
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen)
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
new file mode 100644
index 0000000..9e4c1f6
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
@@ -0,0 +1,26 @@
+package com.android.settingslib.graph
+
+import android.content.Context
+import androidx.preference.PreferenceScreen
+
+/**
+ * Interface to provide [PreferenceScreen].
+ *
+ * It is expected to be implemented by Activity/Fragment and the implementation needs to use
+ * [Context] APIs (e.g. `getContext()`, `getActivity()`) with caution: preference screen creation
+ * could happen in background service, where the Activity/Fragment lifecycle callbacks (`onCreate`,
+ * `onDestroy`, etc.) are not invoked.
+ */
+interface PreferenceScreenProvider {
+
+ /**
+ * Creates [PreferenceScreen].
+ *
+ * Preference screen creation could happen in background service. The implementation MUST use
+ * given [context] instead of APIs like `getContext()`, `getActivity()`, etc.
+ */
+ fun createPreferenceScreen(
+ context: Context,
+ preferenceScreenManager: PreferenceScreenManager,
+ ): PreferenceScreen?
+}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..ea15a67
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-night-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/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 0000000..ea15a67
--- /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 0000000..5192a9a
--- /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 0000000..4b16832
--- /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 81ddf29..1429e3b 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 0000000..eedc364
--- /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 037b80a..b46181e 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 762bb31..f0bcf0a 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_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
new file mode 100644
index 0000000..4ced9f2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_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_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+</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 0000000..eedc364
--- /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 0000000..230eb7d
--- /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 0000000..5bd2a29
--- /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 0000000..3cb3435
--- /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/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
new file mode 100644
index 0000000..285ab73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -0,0 +1,30 @@
+<?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:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
new file mode 100644
index 0000000..e417307
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -0,0 +1,33 @@
+<?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:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="0dp"
+ android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="0dp"
+ android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
new file mode 100644
index 0000000..e964657
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -0,0 +1,30 @@
+<?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:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="1dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
new file mode 100644
index 0000000..a9d69c2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -0,0 +1,33 @@
+<?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:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:bottomLeftRadius="0dp"
+ android:topRightRadius="?android:attr/dialogCornerRadius"
+ android:bottomRightRadius="0dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 5411591..0a36a4f 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 beed90e..8cfe54f 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 229d9e3..7c76ea1 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 fe47e85..7706e0e 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 e4befc2..a9534c3 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 24e3c46..fb637fb 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 3709b5d..185ac3e 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 2691344..2a6499a 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 fff41c3..0000000
--- 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 01dfd7d..cdd5c25 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/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index f73081a..169c330 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 @@
};
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 @@
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 8fd4e91..822a608 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -18,14 +18,18 @@
import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.IDeviceIdleController;
+import android.os.Process;
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 +125,14 @@
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;
}
@@ -163,27 +175,77 @@
/**
* Add app into power save allow list.
- * @param pkg packageName
+ * @param pkg packageName of the app
*/
+ // TODO: Fix all callers to pass in UID
public void addApp(String pkg) {
+ addApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Add app into power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void addApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+
+ if (!wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ true, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
mAllowlistedApps.add(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
/**
* Remove package from power save allow list.
- * @param pkg
+ * @param pkg packageName of the app
*/
public void removeApp(String pkg) {
+ removeApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Remove package from power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void removeApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+ if (wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ false, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
mAllowlistedApps.remove(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index b656253..0d318c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -96,7 +96,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.addApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.addApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -104,7 +104,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(
new String[] {PACKAGE_ONE, PACKAGE_TWO}, UID)).isTrue();
- mPowerAllowlistBackend.removeApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.removeApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -112,7 +112,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.removeApp(PACKAGE_ONE);
+ mPowerAllowlistBackend.removeApp(PACKAGE_ONE, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isFalse();
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index a33c160..75c0cec 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -272,6 +272,7 @@
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
Settings.Secure.AUDIO_DEVICE_INVENTORY,
Settings.Secure.SCREEN_RESOLUTION_MODE,
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 1bff592..8faf917 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -430,5 +430,7 @@
VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.SCREEN_RESOLUTION_MODE, new InclusiveIntegerRangeValidator(
Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL));
+ VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ new InclusiveIntegerRangeValidator(0, 10));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 46cee6b..fa9b279 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1760,6 +1760,9 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER_SATURATION_LEVEL);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
SecureSettingsProto.Accessibility.DISPLAY_INVERSION_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 4cbc18c..f65d797 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -24,10 +24,7 @@
java_library_host {
name: "SystemUILintChecker",
- srcs: [
- "src/**/*.kt",
- "src/**/*.java",
- ],
+ srcs: ["src/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
@@ -38,35 +35,13 @@
java_test_host {
name: "SystemUILintCheckerTest",
- srcs: [
- "tests/**/*.kt",
- "tests/**/*.java",
- ],
+ defaults: ["AndroidLintCheckerTestDefaults"],
+ srcs: ["tests/**/*.kt"],
data: [
":framework",
- ":androidx.annotation_annotation",
+ ":androidx.annotation_annotation-nodeps",
],
static_libs: [
"SystemUILintChecker",
- "junit",
- "lint",
- "lint_tests",
],
- test_options: {
- unit_test: true,
- tradefed_options: [
- {
- // lint bundles in some classes that were built with older versions
- // of libraries, and no longer load. Since tradefed tries to load
- // all classes in the jar to look for tests, it crashes loading them.
- // Exclude these classes from tradefed's search.
- name: "exclude-paths",
- value: "org/apache",
- },
- {
- name: "exclude-paths",
- value: "META-INF",
- },
- ],
- },
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
index 30e2a25..f9bf306 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
@@ -34,7 +34,6 @@
* Checks if any class has implemented the `Dumpable` interface but has not registered itself with
* the `DumpManager`.
*/
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetector : Detector(), SourceCodeScanner {
private var isDumpable: Boolean = false
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 5840e8f..024c394 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -59,7 +59,7 @@
`BroadcastDispatcher` instead, which registers the receiver on a \
background thread. `BroadcastDispatcher` also improves our visibility \
into ANRs.""",
- moreInfo = "go/identifying-broadcast-threads",
+ moreInfo = "http://go/identifying-broadcast-threads",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index 141dd05..f3b24a3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -16,15 +16,8 @@
package com.android.internal.systemui.lint
-import com.android.annotations.NonNull
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
import java.io.File
-import org.intellij.lang.annotations.Language
-
-@Suppress("UnstableApiUsage")
-@NonNull
-private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
/*
* This file contains stubs of framework APIs and System UI classes for testing purposes only. The
@@ -33,16 +26,5 @@
internal val androidStubs =
arrayOf(
LibraryReferenceTestFile(File("framework.jar").canonicalFile),
- LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
- indentedJava(
- """
-package com.android.systemui.settings;
-import android.content.pm.UserInfo;
-
-public interface UserTracker {
- int getUserId();
- UserInfo getUserInfo();
-}
-"""
- ),
+ LibraryReferenceTestFile(File("androidx.annotation_annotation-nodeps.jar").canonicalFile),
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 4c4185d..c9bc8b3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -19,17 +19,14 @@
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
- @Ignore
@Test
fun testBindService() {
lint()
@@ -37,7 +34,9 @@
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
public class TestClass {
public void bind(Context context) {
@@ -48,13 +47,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:9: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
context.bindService(intent, null, 0);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -62,7 +61,6 @@
)
}
- @Ignore
@Test
fun testBindServiceAsUser() {
lint()
@@ -70,7 +68,9 @@
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
import android.os.UserHandle;
public class TestClass {
@@ -82,13 +82,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:10: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -114,7 +114,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -147,7 +147,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -181,7 +181,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -219,12 +219,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 30b68f7..3788dda 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
@@ -30,7 +29,6 @@
@Test
fun testSendBroadcast() {
- println(stubs.size)
lint()
.files(
TestFiles.java(
@@ -47,7 +45,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -80,7 +78,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -114,7 +112,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -149,7 +147,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -176,7 +174,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -201,12 +199,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
index ff150c8c..2c20321 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -21,11 +21,8 @@
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
-@Ignore("b/254533331")
class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector {
return CleanArchitectureDependencyViolationDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index ee6e0ce..0652e69 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -24,7 +24,6 @@
import java.util.EnumSet
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
index 3d6cbc7..6c6c263 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DumpableNotRegisteredDetector()
@@ -37,7 +36,8 @@
class SomeClass() {
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -67,7 +67,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -97,7 +98,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -127,7 +129,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index ed3d14a..bb34d91 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedMainThreadDetector()
@@ -46,7 +45,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -79,7 +78,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -104,7 +103,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -136,7 +135,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -149,6 +148,4 @@
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 846129a..fe5b576 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedServiceDetector()
@@ -44,7 +43,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -76,7 +75,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -109,7 +108,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -134,7 +133,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -147,6 +146,4 @@
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 0ac8f8e..3f12569dfc 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
@@ -35,9 +34,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -48,13 +46,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiver(receiver, filter, 0);
~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -69,9 +67,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
@SuppressWarnings("RegisterReceiverViaContext")
public class TestClass {
@@ -83,7 +80,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
@@ -97,11 +94,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -113,13 +107,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -134,11 +128,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -150,19 +141,17 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverForAllUsers(receiver, filter, "permission",
~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 34a4249..7944ce3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -182,5 +181,19 @@
.expectClean()
}
- private val stubs = androidStubs
+ private val stubs =
+ arrayOf(
+ *androidStubs,
+ java(
+ """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+public interface UserTracker {
+ int getUserId();
+ UserInfo getUserInfo();
+}
+"""
+ )
+ .indented()
+ )
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 34becc6..756751c 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SoftwareBitmapDetector()
@@ -45,7 +44,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -80,7 +79,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -102,12 +101,10 @@
}
"""
),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index efe4c90..fd00018 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = StaticSettingsProviderDetector()
@@ -85,7 +84,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
@@ -226,12 +225,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
index 3f93f07..29b3828 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -10,7 +10,6 @@
import org.junit.runners.JUnit4
import org.junit.runners.model.Statement
-@Suppress("UnstableApiUsage")
@RunWith(JUnit4::class)
abstract class SystemUILintDetectorTest : LintDetectorTest() {
@@ -18,7 +17,7 @@
@ClassRule
@JvmField
val libraryChecker: LibraryExists =
- LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+ LibraryExists("framework.jar", "androidx.annotation_annotation-nodeps.jar")
}
class LibraryExists(vararg val libraryNames: String) : TestRule {
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
index db73154..a4e82a7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
@@ -18,28 +18,22 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() {
- override fun getDetector(): Detector {
- return TestFunctionNameViolationDetector()
- }
+ override fun getDetector(): Detector = TestFunctionNameViolationDetector()
- override fun getIssues(): List<Issue> {
- return listOf(
- TestFunctionNameViolationDetector.ISSUE,
- )
- }
+ override fun getIssues(): List<Issue> = listOf(TestFunctionNameViolationDetector.ISSUE)
@Test
fun violations() {
lint()
.files(
- kotlin(
- """
+ TestFiles.kotlin(
+ """
package test.pkg.name
import org.junit.Test
@@ -64,13 +58,11 @@
}
}
"""
- .trimIndent()
- ),
- testAnnotationStub,
+ )
+ .indented(),
+ testAnnotationStub
)
- .issues(
- TestFunctionNameViolationDetector.ISSUE,
- )
+ .issues(TestFunctionNameViolationDetector.ISSUE)
.run()
.expectWarningCount(0)
.expect(
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 4533f58..356bfe2 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.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.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 @@
viewModel: CommunalViewModel,
dataSourceDelegator: SceneDataSourceDelegator,
dialogFactory: SystemUIDialogFactory,
+ colors: CommunalColors,
) {
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
@@ -135,7 +136,7 @@
emptyMap()
},
) {
- CommunalScene(viewModel, dialogFactory, modifier = modifier)
+ CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
}
}
}
@@ -143,15 +144,18 @@
/** 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/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 5d87a03..94515d3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -250,7 +250,6 @@
)
isDraggingToRemove
},
- onOpenWidgetPicker = onOpenWidgetPicker,
gridState = gridState,
contentListState = contentListState,
selectedKey = selectedKey,
@@ -393,7 +392,6 @@
contentListState: ContentListState,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
@@ -461,7 +459,6 @@
model = list[index],
viewModel = viewModel,
size = size,
- onOpenWidgetPicker = onOpenWidgetPicker,
selected = selected && !isDragging,
widgetConfigurator = widgetConfigurator,
)
@@ -739,7 +736,6 @@
size: SizeF,
selected: Boolean,
modifier: Modifier = Modifier,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator? = null,
) {
when (model) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index dee2559..37fe798 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -161,9 +161,9 @@
private var isOnRemoveButton = false
fun onStarted() {
- // assume item will be added to the second to last position before CTA tile.
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
placeHolderIndex = contentListState.list.size - 1
- placeHolderIndex?.let { contentListState.list.add(it, placeHolder) }
}
fun onMoved(event: DragAndDropEvent) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index dc3b612..418c6bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -44,10 +44,27 @@
internal val scope = SceneScopeImpl(layoutImpl, this)
var content by mutableStateOf(content)
- var userActions by mutableStateOf(actions)
+ private var _userActions by mutableStateOf(checkValid(actions))
var zIndex by mutableFloatStateOf(zIndex)
var targetSize by mutableStateOf(IntSize.Zero)
+ var userActions
+ get() = _userActions
+ set(value) {
+ _userActions = checkValid(value)
+ }
+
+ private fun checkValid(
+ userActions: Map<UserAction, UserActionResult>
+ ): Map<UserAction, UserActionResult> {
+ userActions.forEach { (action, result) ->
+ if (key == result.toScene) {
+ error("Transition to the same scene is not supported. Scene $key, action $action")
+ }
+ }
+ return userActions
+ }
+
@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun Content(modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 723a182..2eaccb4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -48,10 +48,14 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,7 +66,7 @@
private val LayoutSize = 300.dp
}
- private var currentScene by mutableStateOf(TestScenes.SceneA)
+ private var currentScene by mutableStateOf(SceneA)
private lateinit var layoutState: SceneTransitionLayoutState
// We use createAndroidComposeRule() here and not createComposeRule() because we need an
@@ -84,15 +88,15 @@
modifier = Modifier.size(LayoutSize),
) {
scene(
- TestScenes.SceneA,
- userActions = mapOf(Back to TestScenes.SceneB),
+ SceneA,
+ userActions = mapOf(Back to SceneB),
) {
Box(Modifier.fillMaxSize()) {
SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
Text("SceneA")
}
}
- scene(TestScenes.SceneB) {
+ scene(SceneB) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 100.dp,
@@ -102,7 +106,7 @@
Text("SceneB")
}
}
- scene(TestScenes.SceneC) {
+ scene(SceneC) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 150.dp,
@@ -144,42 +148,42 @@
rule.onNodeWithText("SceneB").assertDoesNotExist()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// Change to scene B. Only that scene is displayed.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.onNodeWithText("SceneA").assertDoesNotExist()
rule.onNodeWithText("SceneB").assertIsDisplayed()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testBack() {
rule.setContent { TestContent() }
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
rule.activity.onBackPressed()
rule.waitForIdle()
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testTransitionState() {
rule.setContent { TestContent() }
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// We will advance the clock manually.
rule.mainClock.autoAdvance = false
// Change the current scene. Until composition is triggered, this won't change the layout
// state.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// On the next frame, we will recompose because currentScene changed, which will start the
// transition (i.e. it will change the transitionState to be a Transition) in a
@@ -187,8 +191,8 @@
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
val transition = layoutState.transitionState as TransitionState.Transition
- assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
- assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+ assertThat(transition.fromScene).isEqualTo(SceneA)
+ assertThat(transition.toScene).isEqualTo(SceneB)
assertThat(transition.progress).isEqualTo(0f)
// Then, on the next frame, the animator we started gets its initial value and clock
@@ -216,7 +220,7 @@
// B.
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
@@ -242,7 +246,7 @@
// Go to scene B and let the animation start. See [testLayoutState()] and
// [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
// by 2 frames to be at the start of the animation.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
@@ -251,7 +255,7 @@
// Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
// that B has a higher zIndex than A.
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneB))
// In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
// 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -273,7 +277,7 @@
.of(DpOffset(25.dp, 25.dp))
// Animate to scene C, let the animation start then go to the middle of the transition.
- currentScene = TestScenes.SceneC
+ currentScene = SceneC
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -285,7 +289,7 @@
val expectedLeft = 0.dp
val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
assertThat((layoutState.transitionState as TransitionState.Transition).progress)
.isEqualTo(interpolatedProgress)
sharedFoo.assertWidthIsEqualTo(expectedSize)
@@ -302,15 +306,15 @@
// Wait for the transition to C to finish.
rule.mainClock.advanceTimeBy(TestTransitionDuration)
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC)
// Go back to scene A. This should happen instantly (once the animation started, i.e. after
// 2 frames) given that we use a snap() animation spec.
- currentScene = TestScenes.SceneA
+ currentScene = SceneA
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
}
@Test
@@ -346,4 +350,28 @@
)
}
}
+
+ @Test
+ fun userActionFromSceneAToSceneA_throwsNotSupported() {
+ val exception: IllegalStateException =
+ assertThrows(IllegalStateException::class.java) {
+ rule.setContent {
+ SceneTransitionLayout(
+ state =
+ updateSceneTransitionLayoutState(
+ currentScene = currentScene,
+ onChangeScene = { currentScene = it },
+ transitions = EmptyTestTransitions
+ ),
+ modifier = Modifier.size(LayoutSize),
+ ) {
+ // from SceneA to SceneA
+ scene(SceneA, userActions = mapOf(Back to SceneA), content = {})
+ }
+ }
+ }
+
+ assertThat(exception).hasMessageThat().contains(Back.toString())
+ assertThat(exception).hasMessageThat().contains(SceneA.debugName)
+ }
}
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 f0498de..1501d9c 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 @@
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 0000000..55e46dc
--- /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/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index af8149f..0bd6d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -18,8 +18,15 @@
import static android.view.WindowManager.LayoutParams;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
@@ -30,56 +37,123 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
-class FullscreenMagnificationController {
+class FullscreenMagnificationController implements ComponentCallbacks {
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
private Supplier<SurfaceControlViewHost> mScvhSupplier;
- private SurfaceControlViewHost mSurfaceControlViewHost;
+ private SurfaceControlViewHost mSurfaceControlViewHost = null;
+ private SurfaceControl mBorderSurfaceControl = null;
private Rect mWindowBounds;
private SurfaceControl.Transaction mTransaction;
private View mFullscreenBorder = null;
private int mBorderOffset;
private final int mDisplayId;
private static final Region sEmptyRegion = new Region();
+ private ValueAnimator mShowHideBorderAnimator;
+ private Executor mExecutor;
+ private boolean mFullscreenMagnificationActivated = false;
+ private final Configuration mConfiguration;
FullscreenMagnificationController(
@UiContext Context context,
+ Executor executor,
AccessibilityManager accessibilityManager,
WindowManager windowManager,
Supplier<SurfaceControlViewHost> scvhSupplier) {
+ this(context, executor, accessibilityManager, windowManager, scvhSupplier,
+ new SurfaceControl.Transaction(), createNullTargetObjectAnimator(context));
+ }
+
+ @VisibleForTesting
+ FullscreenMagnificationController(
+ @UiContext Context context,
+ @Main Executor executor,
+ AccessibilityManager accessibilityManager,
+ WindowManager windowManager,
+ Supplier<SurfaceControlViewHost> scvhSupplier,
+ SurfaceControl.Transaction transaction,
+ ValueAnimator valueAnimator) {
mContext = context;
+ mExecutor = executor;
mAccessibilityManager = accessibilityManager;
mWindowManager = windowManager;
mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
- mTransaction = new SurfaceControl.Transaction();
+ mTransaction = transaction;
mScvhSupplier = scvhSupplier;
mBorderOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen_with_offset)
- mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen);
mDisplayId = mContext.getDisplayId();
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
+ mShowHideBorderAnimator = valueAnimator;
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ // The animation was played in reverse, which means we are hiding the border.
+ // We would like to perform clean up after the border is fully hidden.
+ cleanUpBorder();
+ }
+ }
+ });
}
+ private static ValueAnimator createNullTargetObjectAnimator(Context context) {
+ final ValueAnimator valueAnimator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new AccelerateDecelerateInterpolator();
+ final long longAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+
+ valueAnimator.setInterpolator(interpolator);
+ valueAnimator.setDuration(longAnimationDuration);
+ return valueAnimator;
+ }
+
+ /**
+ * Check the fullscreen magnification activation status, and proceed corresponding actions when
+ * there is an activation change.
+ */
@UiThread
void onFullscreenMagnificationActivationChanged(boolean activated) {
- if (activated) {
- createFullscreenMagnificationBorder();
- } else {
- removeFullscreenMagnificationBorder();
+ final boolean changed = (mFullscreenMagnificationActivated != activated);
+ if (changed) {
+ mFullscreenMagnificationActivated = activated;
+ if (activated) {
+ createFullscreenMagnificationBorder();
+ } else {
+ removeFullscreenMagnificationBorder();
+ }
}
}
+ /**
+ * This method should only be called when fullscreen magnification is changed from activated
+ * to inactivated.
+ */
@UiThread
private void removeFullscreenMagnificationBorder() {
+ mContext.unregisterComponentCallbacks(this);
+ mShowHideBorderAnimator.reverse();
+ }
+
+ private void cleanUpBorder() {
if (mSurfaceControlViewHost != null) {
mSurfaceControlViewHost.release();
mSurfaceControlViewHost = null;
@@ -91,31 +165,57 @@
}
/**
- * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
- * and set negative offset to the border view to fill up the spaces between the border and the
- * device corners.
+ * This method should only be called when fullscreen magnification is changed from inactivated
+ * to activated.
*/
@UiThread
private void createFullscreenMagnificationBorder() {
- mFullscreenBorder = LayoutInflater.from(mContext)
- .inflate(R.layout.fullscreen_magnification_border, null);
- mSurfaceControlViewHost = mScvhSupplier.get();
- mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ onConfigurationChanged(mContext.getResources().getConfiguration());
+ mContext.registerComponentCallbacks(this);
- SurfaceControl surfaceControl = mSurfaceControlViewHost
- .getSurfacePackage().getSurfaceControl();
+ if (mSurfaceControlViewHost == null) {
+ // Create the view only if it does not exist yet. If we are trying to enable fullscreen
+ // magnification before it was fully disabled, we use the previous view instead of
+ // creating a new one.
+ mFullscreenBorder = LayoutInflater.from(mContext)
+ .inflate(R.layout.fullscreen_magnification_border, null);
+ // Set the initial border view alpha manually so we won't show the border accidentally
+ // after we apply show() to the SurfaceControl and before the animation starts to run.
+ mFullscreenBorder.setAlpha(0f);
+ mShowHideBorderAnimator.setTarget(mFullscreenBorder);
+ mSurfaceControlViewHost = mScvhSupplier.get();
+ mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
+ }
mTransaction
- .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
- .setLayer(surfaceControl, Integer.MAX_VALUE)
- .show(surfaceControl)
+ .addTransactionCommittedListener(
+ mExecutor,
+ () -> {
+ if (mShowHideBorderAnimator.isRunning()) {
+ // Since the method is only called when there is an activation
+ // status change, the running animator is hiding the border.
+ mShowHideBorderAnimator.reverse();
+ } else {
+ mShowHideBorderAnimator.start();
+ }
+ })
+ .setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
+ .setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
+ .show(mBorderSurfaceControl)
.apply();
- mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+ mAccessibilityManager.attachAccessibilityOverlayToDisplay(
+ mDisplayId, mBorderSurfaceControl);
applyTouchableRegion();
}
+ /**
+ * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+ * and set negative offset to the border view to fill up the spaces between the border and the
+ * device corners.
+ */
private LayoutParams getBorderLayoutParams() {
LayoutParams params = new LayoutParams(
mWindowBounds.width() + 2 * mBorderOffset,
@@ -137,4 +237,41 @@
// all touch events to go through this view.
surfaceControl.setTouchableRegion(sEmptyRegion);
}
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ final int configDiff = newConfig.diff(mConfiguration);
+ mConfiguration.setTo(newConfig);
+ onConfigurationChanged(configDiff);
+ }
+
+ @VisibleForTesting
+ void onConfigurationChanged(int configDiff) {
+ boolean reCreateWindow = false;
+ if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0
+ || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
+ || (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ updateDimensions();
+ mWindowBounds.set(mWindowManager.getCurrentWindowMetrics().getBounds());
+ reCreateWindow = true;
+ }
+
+ if (mFullscreenBorder != null && reCreateWindow) {
+ final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
+ final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
+ mSurfaceControlViewHost.relayout(newWidth, newHeight);
+ }
+ }
+
+ private void updateDimensions() {
+ mBorderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ }
+
+ @Override
+ public void onLowMemory() {
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 70165f3..177d933 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -54,6 +54,7 @@
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -71,6 +72,7 @@
private final ModeSwitchesController mModeSwitchesController;
private final Context mContext;
private final Handler mHandler;
+ private final Executor mExecutor;
private final AccessibilityManager mAccessibilityManager;
private final CommandQueue mCommandQueue;
private final OverviewProxyService mOverviewProxyService;
@@ -139,12 +141,13 @@
DisplayIdIndexSupplier<FullscreenMagnificationController> {
private final Context mContext;
+ private final Executor mExecutor;
- FullscreenMagnificationControllerSupplier(Context context, Handler handler,
- DisplayManager displayManager, SysUiState sysUiState,
- SecureSettings secureSettings) {
+ FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
+ Executor executor) {
super(displayManager);
mContext = context;
+ mExecutor = executor;
}
@Override
@@ -156,6 +159,7 @@
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
return new FullscreenMagnificationController(
windowContext,
+ mExecutor,
windowContext.getSystemService(AccessibilityManager.class),
windowContext.getSystemService(WindowManager.class),
scvhSupplier);
@@ -200,13 +204,14 @@
DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
@Inject
- public Magnification(Context context, @Main Handler mainHandler,
+ public Magnification(Context context, @Main Handler mainHandler, @Main Executor executor,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger) {
mContext = context;
mHandler = mainHandler;
+ mExecutor = executor;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
mModeSwitchesController = modeSwitchesController;
@@ -218,7 +223,7 @@
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
- context, mHandler, displayManager, sysUiState, secureSettings);
+ context, displayManager, mExecutor);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
mMagnificationSettingsControllerCallback, displayManager, secureSettings);
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index 179fa87..eaca276 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -1,6 +1,9 @@
package com.android.systemui.bouncer.ui.binder
import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -30,7 +33,24 @@
) {
view.addView(
ComposeView(view.context).apply {
- setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle =
+ this@repeatWhenAttached.lifecycle
+ }
+ )
+ setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ }
+ }
}
)
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 72dcb26..27af99e 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.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 @@
@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/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index b3002cd..3f92223 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -63,7 +63,7 @@
override val isEditMode = true
- // Only widgets are editable. The CTA tile comes last in the list and remains visible.
+ // Only widgets are editable.
override val communalContent: Flow<List<CommunalContentModel>> =
communalInteractor.widgetContent.onEach { models ->
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
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 bdf4e72..1bee83b 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 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 @@
class CommunalTransitionViewModel
@Inject
constructor(
+ communalColors: CommunalColors,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
@@ -68,4 +72,13 @@
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 0000000..1e04fe7
--- /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 db6b8fe..9ada1ef 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 @@
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/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index db4a7fa..b50ee57 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
&& !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 @@
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 @@
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 @@
mPipOptional = pipOptional;
mDesktopModeOptional = desktopModeOptional;
mFalsingManager = falsingManager;
- mNavBarEdgePanelProvider = navBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
- mFeatureFlags = featureFlags;
mLightBarControllerProvider = lightBarControllerProvider;
}
@@ -1391,9 +1363,7 @@
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 380846e..0000000
--- 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 10a88c8..b46f2d2 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 @@
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 9698548d..54a59f30 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 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 @@
* 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 @@
// 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 ec7707c..e56a4f4 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 @@
interface TakeScreenshotExecutor {
suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
)
fun onCloseSystemDialogsReceived()
@@ -30,7 +30,7 @@
fun onDestroy()
fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback
)
}
@@ -65,7 +65,7 @@
*/
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
) {
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
@@ -86,7 +86,7 @@
/** 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 @@
/** 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 d62ab85..1945c25 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 @@
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 @@
.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 3789371..f768cfb 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.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 b781ae9..fdf16aa 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.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 @@
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 @@
}
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 f418e7e..8f6b9d0 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.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 @@
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 @@
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 b8512f2..aa915e3 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.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 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 @@
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 @@
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 @@
ActivityStarter activityStarter,
SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
ActiveNotificationsInteractor activeNotificationsInteractor,
+ HeadsUpNotificationInteractor headsUpNotificationInteractor,
ShadeAnimationInteractor shadeAnimationInteractor,
KeyguardViewConfigurator keyguardViewConfigurator,
DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@@ -804,6 +807,7 @@
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
mActiveNotificationsInteractor = activeNotificationsInteractor;
+ mHeadsUpNotificationInteractor = headsUpNotificationInteractor;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -886,7 +890,6 @@
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = predictiveBackAnimateShade();
- mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
mFalsingCollector = falsingCollector;
mWakeUpCoordinator = coordinator;
mMainDispatcher = mainDispatcher;
@@ -1216,6 +1219,11 @@
}
},
mMainDispatcher);
+
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(),
+ setHeadsUpVisible(), mMainDispatcher);
+ }
}
@VisibleForTesting
@@ -3055,7 +3063,21 @@
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 @@
}
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 @@
}
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 @@
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 @@
private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
@Override
public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
+
if (inPinnedMode) {
mHeadsUpExistenceChangedRunnable.run();
updateNotificationTranslucency();
@@ -4400,9 +4429,7 @@
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- return;
- }
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (!isKeyguardShowing()) {
mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
@@ -4411,9 +4438,7 @@
@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 @@
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 @@
mIsTrackpadReverseScroll =
!mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()
- && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event);
+ && isTrackpadScroll(event);
if (!isTracking() || isFullyCollapsed()) {
mInitialExpandY = y;
mInitialExpandX = x;
@@ -5143,9 +5167,8 @@
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 a763641..907cf5e 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 @@
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 @@
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mGlanceableHubContainerController = glanceableHubContainerController;
- mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -643,16 +640,10 @@
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 e3db626..222b070 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.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 @@
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 77660eb..e9306a5 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 @@
* 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 7f94da3..98b52ed 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.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 @@
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 bb65651..8a1a4f1 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.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 @@
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 @@
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 @@
void onChildAnimationFinished() {
setAnimationRunning(false);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(false);
+ }
requestChildrenUpdate();
runAnimationFinishedRunnables();
clearTransient();
@@ -4717,6 +4730,7 @@
}
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
generateHeadsUpAnimation(row, isHeadsUp);
}
@@ -4750,6 +4764,9 @@
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(true);
+ }
}
requestChildrenUpdate();
}
@@ -4939,11 +4956,28 @@
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;
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 06479e5..ea72c9b 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 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 70aa245..3a89630 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
@@ -299,4 +299,8 @@
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 b3d8430..6acb12a 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
@@ -16,13 +16,17 @@
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
class HeadsUpNotificationViewBinder
@@ -62,9 +66,19 @@
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 9268d16..6546db9 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.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 @@
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 0ddf37d..8ec8d1c 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 @@
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 @@
}
private void onShadeOrQsExpanded(Boolean isExpanded) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
@@ -500,7 +504,7 @@
@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 38b3718..3343779 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 @@
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 87139ac..da5877b 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.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 @@
// 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/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 6b237f8e..f19fa20 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,6 +35,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -53,32 +55,40 @@
private val uiEventLogger: UiEventLogger,
) {
- private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
+ private val sessionWithPlayback: StateFlow<Result<SessionWithPlayback?>> =
interactor.defaultActiveMediaSession
.flatMapLatest { session ->
if (session == null) {
- flowOf(null)
+ flowOf(Result.Data<SessionWithPlayback?>(null))
} else {
- mediaDeviceSessionInteractor.playbackState(session).map { playback ->
- playback?.let { SessionWithPlayback(session, it) }
- }
+ mediaDeviceSessionInteractor
+ .playbackState(session)
+ .map { playback ->
+ playback?.let {
+ Result.Data<SessionWithPlayback?>(SessionWithPlayback(session, it))
+ }
+ }
+ .filterNotNull()
}
}
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
- null,
+ Result.Loading(),
)
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
ConnectedDeviceViewModel(
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
context.getString(
R.string.media_output_label_title,
- mediaDeviceSession.session.appLabel
+ mediaDeviceSession.data.session.appLabel
)
} else {
context.getString(R.string.media_output_title_without_playing)
@@ -96,7 +106,10 @@
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
val icon =
currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
?: Icon.Resource(
@@ -130,6 +143,7 @@
fun onBarClick(expandable: Expandable) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
- actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
+ val result = sessionWithPlayback.value
+ actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
new file mode 100644
index 0000000..8793538
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.volume.panel.shared.model
+
+/** Models a loadable result */
+sealed interface Result<T> {
+
+ /** The data is still loading */
+ class Loading<T> : Result<T>
+
+ /** The data is loaded successfully */
+ data class Data<T>(val data: T) : Result<T>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e48b639..263ddc1 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.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.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 @@
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 @@
UserTracker userTracker,
DisplayTracker displayTracker,
NoteTaskInitializer noteTaskInitializer,
+ CommunalTransitionViewModel communalTransitionViewModel,
+ JavaAdapter javaAdapter,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -205,6 +211,8 @@
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
+ mCommunalTransitionViewModel = communalTransitionViewModel;
+ mJavaAdapter = javaAdapter;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -381,6 +389,8 @@
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/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 12f334b..5bc9aa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -16,49 +16,84 @@
package com.android.systemui.accessibility;
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.window.InputTransferToken;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class FullscreenMagnificationControllerTest extends SysuiTestCase {
-
+ private static final long ANIMATION_DURATION_MS = 100L;
+ private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
+ private static final long ANIMATION_TIMEOUT_MS =
+ 5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
private FullscreenMagnificationController mFullscreenMagnificationController;
private SurfaceControlViewHost mSurfaceControlViewHost;
+ private ValueAnimator mShowHideBorderAnimator;
+ private SurfaceControl.Transaction mTransaction;
+ private TestableWindowManager mWindowManager;
@Before
public void setUp() {
getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
- new SurfaceControlViewHost(mContext, mContext.getDisplay(),
- new InputTransferToken(), "FullscreenMagnification"));
-
+ spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ new InputTransferToken(), "FullscreenMagnification")));
Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = new TestableWindowManager(wm);
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ mTransaction = new SurfaceControl.Transaction();
+ mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
mFullscreenMagnificationController = new FullscreenMagnificationController(
mContext,
+ mContext.getMainExecutor(),
mContext.getSystemService(AccessibilityManager.class),
mContext.getSystemService(WindowManager.class),
- scvhSupplier);
+ scvhSupplier,
+ mTransaction,
+ mShowHideBorderAnimator);
}
@After
@@ -69,29 +104,143 @@
}
@Test
- public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true)
- );
-
- // Wait for Rects updated.
- waitForIdleSync();
+ public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
}
@Test
- public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> {
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true);
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(false);
+ public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
+ CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ disableAnimationEndLatch.countDown();
+ } else {
+ enableAnimationEndLatch.countDown();
}
- );
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for enabling animation to be finished",
+ enableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
- assertThat(mSurfaceControlViewHost.getView()).isNull();
+ getInstrumentation().runOnMainSync(() ->
+ // Disable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false));
+
+ assertTrue("Failed to wait for disabling animation to be finished",
+ disableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ verify(mSurfaceControlViewHost).release();
}
+ @Test
+ public void onFullscreenMagnificationActivationChangeTrue_deactivating_reverseAnimator()
+ throws InterruptedException {
+ // Simulate the hiding border animation is running
+ when(mShowHideBorderAnimator.isRunning()).thenReturn(true);
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ }
+
+ @Test
+ public void onScreenSizeChanged_activated_borderChangedToExpectedSize()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+ mWindowManager.setWindowBounds(testWindowBounds);
+
+ getInstrumentation().runOnMainSync(() ->
+ mFullscreenMagnificationController.onConfigurationChanged(
+ ActivityInfo.CONFIG_SCREEN_SIZE));
+
+ int borderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ final int newWidth = testWindowBounds.width() + 2 * borderOffset;
+ final int newHeight = testWindowBounds.height() + 2 * borderOffset;
+ verify(mSurfaceControlViewHost).relayout(newWidth, newHeight);
+ }
+
+ private ValueAnimator newNullTargetObjectAnimator() {
+ final ValueAnimator animator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new DecelerateInterpolator(2.5f);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(ANIMATION_DURATION_MS);
+ return animator;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 41d5d5d..25e5470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -101,7 +101,7 @@
}).when(mAccessibilityManager).setMagnificationConnection(
any(IMagnificationConnection.class));
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 3b5cbea..6dc5b72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -121,7 +121,8 @@
mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(),
+ mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
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 c900463..0f37143 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0776aa7..77b5c91 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 @@
override fun onCloseSystemDialogsReceived() {}
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback,
) {
requestReceived = screenshotRequest
@@ -248,7 +248,7 @@
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 621b058..254f1e1 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 @@
* 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 0000000..9e3ae05
--- /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 0000000..5d35528
--- /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 e611da0..ee03236 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.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 @@
@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 @@
keyguardInteractor,
shadeInteractor,
powerManager,
+ communalColors,
kosmos.sceneDataSourceDelegator,
)
testableLooper = TestableLooper.get(this)
@@ -156,6 +159,7 @@
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 0a8e470..7a39a0d 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.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.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 @@
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 @@
@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 @@
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 650c45b..81e20c1 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.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.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 @@
}
@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 4df7ef5..6631d29 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.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 @@
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 8c5a4d0..da09579 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.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 @@
.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 ba8eb6f..f380b6c 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 @@
.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 1e058ca..89ae9f4 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.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.MockitoRule;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Tests for {@link NotificationStackScrollLayout}.
@@ -1044,6 +1046,96 @@
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 @@
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 @@
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 d2c8aea..0000000
--- 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 e36ddc1..e3c218d 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 @@
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 0000000..e76cf68
--- /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 0000000..7046658
--- /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 dc1b9fe..7bf77e5 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 @@
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/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 8ab2e0f..5846c92 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -91,6 +91,16 @@
}
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 ccf9a90..cadbd5e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4623,6 +4623,20 @@
android.Manifest.permission.STATUS_BAR_SERVICE);
getMagnificationConnectionManager().setConnection(connection);
+
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && connection == null
+ && mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
+ // Since the connection does not exist, the system ui cannot provide the border
+ // implementation for fullscreen magnification. So we call reset to deactivate the
+ // fullscreen magnification to prevent the magnified but no border situation.
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
+ getMagnificationController().getFullScreenMagnificationController()
+ .reset(display.getDisplayId(), false);
+ }
+ }
}
/**
@@ -5234,7 +5248,14 @@
//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 b119d7d..853b824 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 @@
* 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 @@
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
synchronized (mLock) {
if (interactionId == mInteractionId) {
@@ -114,6 +115,7 @@
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mLock) {
@@ -132,6 +134,7 @@
}
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
int interactionId)
throws RemoteException {
@@ -163,6 +166,7 @@
}
@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 @@
}
@Override
+ @RequiresNoPermission
public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
throws RemoteException {
mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
@@ -285,6 +290,7 @@
}
@Override
+ @RequiresNoPermission
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId)
throws RemoteException {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a5bbc7e..aa0af7e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -118,6 +118,7 @@
private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
+ @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -682,6 +683,12 @@
if (!mRegistered) {
return false;
}
+ // If the border implementation is on system ui side but the connection is not
+ // established, the fullscreen magnification should not work.
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && !mMagnificationConnectionStateSupplier.get()) {
+ return false;
+ }
if (DEBUG) {
Slog.i(LOG_TAG,
"setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
@@ -941,7 +948,8 @@
@NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider,
- @NonNull Executor backgroundExecutor) {
+ @NonNull Executor backgroundExecutor,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
this(
new ControllerContext(
context,
@@ -955,7 +963,8 @@
/* thumbnailSupplier= */ null,
backgroundExecutor,
() -> new Scroller(context),
- TimeAnimator::new);
+ TimeAnimator::new,
+ magnificationConnectionStateSupplier);
}
/** Constructor for tests */
@@ -968,11 +977,13 @@
Supplier<MagnificationThumbnail> thumbnailSupplier,
@NonNull Executor backgroundExecutor,
Supplier<Scroller> scrollerSupplier,
- Supplier<TimeAnimator> timeAnimatorSupplier) {
+ Supplier<TimeAnimator> timeAnimatorSupplier,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
mControllerCtx = ctx;
mLock = lock;
mScrollerSupplier = scrollerSupplier;
mTimeAnimatorSupplier = timeAnimatorSupplier;
+ mMagnificationConnectionStateSupplier = magnificationConnectionStateSupplier;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
addInfoChangedCallback(magnificationInfoChangedCallback);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 20bec59..76367a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -798,7 +798,9 @@
mLock,
this,
mScaleProvider,
- mBackgroundExecutor
+ mBackgroundExecutor,
+ () -> (isMagnificationConnectionManagerInitialized()
+ && getMagnificationConnectionManager().isConnected())
);
}
}
@@ -831,6 +833,12 @@
}
}
+ private boolean isMagnificationConnectionManagerInitialized() {
+ synchronized (mLock) {
+ return mMagnificationConnectionManager != null;
+ }
+ }
+
private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
if (mMagnificationConnectionManager == null
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 70ecc05..590a1ef 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -43,8 +43,15 @@
}
flag {
- name: "include_last_focused_id_and_session_id_in_client_state"
+ name: "add_last_focused_id_to_client_state"
namespace: "autofill"
- description: "Include the current view id and session id into the FillEventHistory as part of ClientState"
+ description: "Include the current view id into the FillEventHistory events as part of ClientState"
bug: "334141398"
}
+
+flag {
+ name: "add_session_id_to_client_state"
+ namespace: "autofill"
+ description: "Include the session id into the FillEventHistory events as part of ClientState"
+ bug: "333927465"
+}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 82e9a26..7a2106b 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 @@
// 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 9cfb535..c892b84 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 @@
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 a18776e..1f09d4d 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 @@
@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 ae2b708..29e8095 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 @@
@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 @@
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 @@
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 @@
/**
* 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 @@
}
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 @@
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
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java
rename to 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 bdc4a7a..2545620 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -22,6 +22,7 @@
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 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
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
rename to services/core/java/com/android/server/PackageWatchdog.java
index 75a8bdf..6f20adf 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.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
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/RescueParty.java
rename to services/core/java/com/android/server/RescueParty.java
index f86eb61..271d552 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.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.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 1e7bc39..6c7546e 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_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 @@
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 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 @@
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 @@
mProjectionActive = false;
if (mMediaProjectionSession != null) {
mMediaProjectionSession.logProjectionSessionStop();
+ if (sensitiveContentImprovements()) {
+ mMediaProjectionSession.logAppNotificationsProtected();
+ }
mMediaProjectionSession = null;
}
@@ -334,9 +375,14 @@
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 @@
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 67e18ca..4dd3a8f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -149,7 +149,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
@@ -3278,7 +3277,7 @@
throws RemoteException {
super.setCeStorageProtection_enforcePermission();
- mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
+ mVold.setCeStorageProtection(userId, secret);
}
/* Only for use by LockSettingsService */
@@ -3288,7 +3287,7 @@
super.unlockCeStorage_enforcePermission();
if (StorageManager.isFileEncrypted()) {
- mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
+ mVold.unlockCeStorage(userId, secret);
}
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ea82b0..e47d416 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5725,14 +5725,11 @@
bringDownServiceLocked(r, enqueueOomAdj);
return msg;
}
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- true);
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app,
+ r);
if (isolated) {
r.isolationHostProc = app;
}
- } else {
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- false);
}
if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7739847..4568624 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -37,6 +37,9 @@
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
@@ -497,6 +500,8 @@
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 +688,6 @@
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 +2599,6 @@
BackgroundThread.getHandler(), this);
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
- mOomAdjProfiler.batteryPowerChanged(mOnBattery);
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
@@ -2845,13 +2847,12 @@
updateCpuStatsNow();
synchronized (mProcLock) {
mOnBattery = DEBUG_POWER ? true : onBattery;
- mOomAdjProfiler.batteryPowerChanged(onBattery);
}
}
@Override
public void batteryStatsReset() {
- mOomAdjProfiler.reset();
+ // Empty for now.
}
@Override
@@ -5107,10 +5108,19 @@
} // else, stopped packages in private space may still hit the logic below
}
}
+
+ final boolean wasForceStopped = app.wasForceStopped()
+ || app.getWindowProcessController().wasForceStopped();
+ if (android.app.Flags.appRestrictionsApi() && wasForceStopped) {
+ noteAppRestrictionEnabled(app.info.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_USAGE, "unknown", 0L);
+ }
+
if (!sendBroadcast) {
if (!android.content.pm.Flags.stayStopped()) return;
// Nothing to do if it wasn't previously stopped
- if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+ if (!wasForceStopped) {
return;
}
}
@@ -7417,7 +7427,6 @@
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEvent();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
- mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
@@ -8949,8 +8958,10 @@
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 +9856,11 @@
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 +10558,6 @@
pw.println(
"-------------------------------------------------------------------------------");
}
- mOomAdjProfiler.dump(pw);
- pw.println();
- if (dumpAll) {
- pw.println(
- "-------------------------------------------------------------------------------");
- }
dumpLmkLocked(pw);
}
pw.println();
@@ -14331,6 +14341,20 @@
int newBackupUid;
synchronized(this) {
+ if (android.app.Flags.appRestrictionsApi()) {
+ try {
+ final boolean wasStopped = mPackageManagerInt.isPackageStopped(app.packageName,
+ UserHandle.getUserId(app.uid));
+ if (wasStopped) {
+ noteAppRestrictionEnabled(app.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_DEFAULT, "restore", 0L);
+ }
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "No such package", e);
+ }
+ }
+
// !!! TODO: currently no check here that we're already bound
// Backup agent is now in use, its package can't be stopped.
try {
@@ -20116,6 +20140,34 @@
}
/**
+ * Log the reason for changing an app restriction. Purely used for logging purposes and does not
+ * cause any change to app state.
+ *
+ * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long)
+ */
+ @Override
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @ActivityManager.RestrictionReason int reason, String subReason, long threshold) {
+ if (!android.app.Flags.appRestrictionsApi()) return;
+
+ enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
+ "noteAppRestrictionEnabled()");
+
+ final int userId = UserHandle.getCallingUserId();
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ if (uid == -1) {
+ uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+ }
+ mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
+ enabled, reason, subReason, threshold);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
* Get an app's background restriction level.
* This interface is intended for the shell command to use.
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 372ec45..5b15c37 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4006,8 +4006,12 @@
return ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
case "background_restricted":
return ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
- case "hibernation":
- return ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+ case "force_stopped":
+ return ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+ case "user_launch_only":
+ return ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+ case "custom":
+ return ActivityManager.RESTRICTION_LEVEL_CUSTOM;
default:
return ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 117221f..c5cad14 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -23,11 +23,20 @@
import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
-import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT;
+import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER;
+import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED;
+import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.app.ActivityManager.UID_OBSERVER_IDLE;
@@ -93,6 +102,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager.RestrictionLevel;
+import android.app.ActivityManager.RestrictionReason;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.AppOpsManager;
@@ -344,6 +354,7 @@
static final int TRACKER_TYPE_PERMISSION = 5;
static final int TRACKER_TYPE_BROADCAST_EVENTS = 6;
static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7;
+
@IntDef(prefix = { "TRACKER_TYPE_" }, value = {
TRACKER_TYPE_UNKNOWN,
TRACKER_TYPE_BATTERY,
@@ -1714,7 +1725,7 @@
String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
boolean allowRequestBgRestricted, boolean calcTrackers) {
if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
- return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, mEmptyTrackerInfo);
+ return new Pair<>(RESTRICTION_LEVEL_FORCE_STOPPED, mEmptyTrackerInfo);
}
@RestrictionLevel int level;
TrackerInfo trackerInfo = null;
@@ -2034,7 +2045,7 @@
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case RESTRICTION_LEVEL_HIBERNATION:
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
@@ -2354,6 +2365,85 @@
}
}
+ /**
+ * Log a change in restriction state with a reason and threshold.
+ * @param packageName
+ * @param uid
+ * @param restrictionType
+ * @param enabled
+ * @param reason
+ * @param subReason Eg: settings, cli, long_wakelock, crash, binder_spam, cpu, threads
+ * Length should not exceed RESTRICTON_SUBREASON_MAX_LENGTH
+ * @param threshold
+ */
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @RestrictionReason int reason, String subReason, long threshold) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to "
+ + restrictionType + " reason=" + reason + ", subReason=" + subReason
+ + ", threshold=" + threshold);
+ }
+
+ // Limit the length of the free-form subReason string
+ if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) {
+ subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH);
+ Slog.e(TAG, "Subreason is too long, truncating: " + subReason);
+ }
+
+ // Log the restriction reason
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED, uid,
+ getRestrictionTypeStatsd(restrictionType),
+ enabled,
+ getRestrictionChangeReasonStatsd(reason, subReason),
+ subReason,
+ threshold);
+ }
+
+ private int getRestrictionTypeStatsd(@RestrictionLevel int level) {
+ return switch (level) {
+ case RESTRICTION_LEVEL_UNKNOWN ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNKNOWN;
+ case RESTRICTION_LEVEL_UNRESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNRESTRICTED;
+ case RESTRICTION_LEVEL_EXEMPTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_EXEMPTED;
+ case RESTRICTION_LEVEL_ADAPTIVE_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_ADAPTIVE;
+ case RESTRICTION_LEVEL_RESTRICTED_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_RESTRICTED_BUCKET;
+ case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_BACKGROUND_RESTRICTED;
+ case RESTRICTION_LEVEL_FORCE_STOPPED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_FORCE_STOPPED;
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_USER_LAUNCH_ONLY;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_CUSTOM;
+ };
+ }
+
+ private int getRestrictionChangeReasonStatsd(int reason, String subReason) {
+ return switch (reason) {
+ case RESTRICTION_REASON_DEFAULT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DEFAULT;
+ case RESTRICTION_REASON_DORMANT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DORMANT;
+ case RESTRICTION_REASON_USAGE ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE;
+ case RESTRICTION_REASON_USER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER;
+ case RESTRICTION_REASON_USER_NUDGED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED;
+ case RESTRICTION_REASON_SYSTEM_HEALTH ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH;
+ case RESTRICTION_REASON_REMOTE_TRIGGER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER;
+ };
+ }
+
static class NotificationHelper {
static final String PACKAGE_SCHEME = "package";
static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 0728ea8..2be1fe2 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -196,6 +196,8 @@
start.setIntent(intent);
start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
+
+ // TODO: handle possible alarm activity start.
if (intent != null && intent.getCategories() != null
&& intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
@@ -313,7 +315,7 @@
}
public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
- ServiceRecord serviceRecord, boolean cold) {
+ ServiceRecord serviceRecord) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -323,8 +325,9 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+
+ // TODO: handle possible alarm service start.
start.setReason(serviceRecord.permission != null
&& serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
? ApplicationStartInfo.START_REASON_JOB
@@ -336,8 +339,9 @@
}
}
- public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app,
- BroadcastRecord broadcast, boolean cold) {
+ /** Process a broadcast triggered app start. */
+ public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, Intent intent,
+ boolean isAlarm) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -347,26 +351,19 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
- if (broadcast == null) {
- start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
- } else if (broadcast.alarm) {
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+ if (isAlarm) {
start.setReason(ApplicationStartInfo.START_REASON_ALARM);
- } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) {
- start.setReason(ApplicationStartInfo.START_REASON_PUSH);
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) {
- start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE);
} else {
start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
}
- start.setIntent(broadcast != null ? broadcast.intent : null);
+ start.setIntent(intent);
addStartInfoLocked(start);
}
}
- public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app,
- boolean cold) {
+ /** Process a content provider triggered app start. */
+ public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -376,8 +373,7 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
addStartInfoLocked(start);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c082889..48dd039 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1030,6 +1030,10 @@
"startProcessLocked failed");
return true;
}
+ // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent.
+ mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart(
+ SystemClock.elapsedRealtimeNanos(), queue.app, r.getReceiverIntent(receiver),
+ r.alarm /* isAlarm */);
return false;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f76bf37..28fd197 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -179,6 +179,7 @@
final int expectedUserId = userId;
synchronized (mService) {
long startTime = SystemClock.uptimeMillis();
+ long startElapsedTimeNs = SystemClock.elapsedRealtimeNanos();
ProcessRecord r = null;
if (caller != null) {
@@ -585,6 +586,8 @@
callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT,
firstLaunch,
0L /* TODO: stoppedDuration */);
+ mService.mProcessList.getAppStartInfoTracker()
+ .handleProcessContentProviderStart(startElapsedTimeNs, proc);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
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 0869114..0000000
--- 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 ea7a21d..9b72db8 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 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
final ProcessStateRecord state = app.mState;
@@ -630,7 +629,6 @@
}
mTmpProcessList.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return true;
}
@@ -849,7 +847,6 @@
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 @@
processes.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -895,7 +891,6 @@
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 @@
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 00e1482..3268b66 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 @@
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 @@
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 dd4cee4..b703076 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_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 @@
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 @@
@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 @@
}
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 @@
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 @@
// 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 @@
// 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 @@
}
}
+ /**
+ * 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 @@
uss.switching = false;
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
+ scheduleStopOfBackgroundUser(oldUserId);
t.traceEnd(); // end continueUserSwitch
}
@@ -3309,6 +3391,8 @@
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 @@
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/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index a17b3d5..ce41079 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -461,6 +461,9 @@
packageName, PACKAGE_MATCH_FLAGS, userId);
StorageStats stats = mStorageStatsManager.queryStatsForPackage(
info.storageUuid, packageName, new UserHandle(userId));
+ if (android.app.Flags.appRestrictionsApi()) {
+ noteHibernationChange(packageName, info.uid, true);
+ }
mIActivityManager.forceStopPackage(packageName, userId);
mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
null /* observer */);
@@ -490,6 +493,11 @@
// Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
// their alarms/jobs/etc.
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ ApplicationInfo info = mIPackageManager.getApplicationInfo(
+ packageName, PACKAGE_MATCH_FLAGS, userId);
+ noteHibernationChange(packageName, info.uid, false);
+ }
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
@@ -555,6 +563,26 @@
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ /** Inform ActivityManager that the app being stopped or unstopped due to hibernation */
+ private void noteHibernationChange(String packageName, int uid, boolean hibernated) {
+ try {
+ if (hibernated) {
+ // TODO: Switch to an ActivityManagerInternal API
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
+ /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
+ } else {
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ false, ActivityManager.RESTRICTION_REASON_USAGE, null,
+ 0L);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't set restriction state change");
+ }
+ }
+
/**
* Initializes in-memory store of user-level hibernation states for the given user
*
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a1f80d0..1bd93e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -5543,6 +5543,20 @@
}
return 0;
}
+ case "note": {
+ int res = shell.parseUserPackageOp(true, err);
+ if (res < 0) {
+ return res;
+ }
+ if (shell.packageName != null) {
+ shell.mInterface.noteOperation(shell.op, shell.packageUid,
+ shell.packageName, shell.attributionTag, true,
+ "appops note shell command", true);
+ } else {
+ return -1;
+ }
+ return 0;
+ }
case "start": {
int res = shell.parseUserPackageOp(true, err);
if (res < 0) {
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 9f4b3d2..c393e92 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1651,7 +1651,7 @@
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case ActivityManager.RESTRICTION_LEVEL_HIBERNATION:
+ case ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index df5f007..68e2bd6 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 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 3c2918f..b24caf4 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 @@
@NonNull
ExternalDisplayStatsService getExternalDisplayStatsService();
-
- void onExternalDisplayReadyToBeEnabled(int displayId);
}
@NonNull
@@ -187,10 +185,6 @@
return;
}
- if (enabled) {
- mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked());
- }
-
mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
}
@@ -223,7 +217,6 @@
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 90d3345..fa42316 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 @@
}
/**
- * 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 {
@@ -1372,13 +1365,6 @@
}
}
-
- void onExternalDisplayReadyToBeEnabled(int displayId) {
- DisplayInfo displayInfo = getDisplayInfo(displayId);
- updateDisplaysPeakRefreshRateAndResolution(displayInfo);
- addDisplaysSynchronizedPeakRefreshRate(displayInfo);
- }
-
@Override
public void onDisplayAdded(int displayId) {
updateDisplayDeviceConfig(displayId);
@@ -1386,6 +1372,8 @@
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
updateUserSettingDisplayPreferredSize(displayInfo);
+ updateDisplaysPeakRefreshRateAndResolution(displayInfo);
+ addDisplaysSynchronizedPeakRefreshRate(displayInfo);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2583d73..aa18175 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1099,7 +1099,7 @@
imesToClearAdditionalSubtypes.add(imiId);
}
int change = isPackageDisappearing(imi.getPackageName());
- if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) {
+ if (change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
if (isCurrentUser) {
setInputMethodEnabledLocked(imi.getId(), false);
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 71a9f54..32a0ef4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -38,6 +38,7 @@
import android.util.Log;
import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.server.FgThread;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -200,7 +201,12 @@
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
if (subManager != null) {
- subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ if (Flags.subscriptionsListenerThread()) {
+ subManager.addOnSubscriptionsChangedListener(FgThread.getExecutor(),
+ mOnSubscriptionsChangeListener);
+ } else {
+ subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ }
}
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
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 bd0501d..20c5b5f 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 @@
}
@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 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
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
rename to 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
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
rename to 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 5b501e1..587be07 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.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 @@
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 @@
}
@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 @@
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 e89ddfd..34d18ce 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 @@
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 @@
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 8d8d596..d69150d 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 @@
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 @@
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 03822aa..c655d46 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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
}
if (!mQuotaLimiter.acquire()) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten);
+ }
return true;
}
mRateLimiter.acquire();
@@ -133,12 +144,16 @@
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 0000000..0092c37
--- /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 8a661bc..d46e891 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.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 @@
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 @@
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 {
- LogsCollectorJob(JobService auditLogService, JobParameters params) {
- mAuditLogService = auditLogService;
- mParams = params;
+ private final JobScheduler mJobScheduler;
+
+ 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 4a81c95..440d251 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 @@
* @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 cddc79d..0afb049 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 @@
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 b7d8cfc..e944eca 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 @@
@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 @@
@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 @@
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 @@
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/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 1383708..6a4c9c2 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.IpSecTransformState;
import android.net.vcn.FeatureFlags;
import android.net.vcn.FeatureFlagsImpl;
import android.os.Looper;
@@ -34,7 +35,6 @@
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
- @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -49,7 +49,6 @@
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
- mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
}
@NonNull
@@ -76,7 +75,16 @@
}
public boolean isFlagIpSecTransformStateEnabled() {
- return mCoreNetFeatureFlags.ipsecTransformState();
+ // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+ // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
+ // right now. We should either update the code when the flag is accessible or remove the
+ // legacy behavior after VIC SDK finalization
+ try {
+ new IpSecTransformState.Builder();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
}
@NonNull
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index b33fa6f..f82ff67 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 @@
}
@Override
- public void registerVibratorController(IVibratorController controller) {
+ public void registerVibratorController(@NonNull IVibratorController controller) {
+ Objects.requireNonNull(controller);
+
synchronized (mLock) {
mVibratorControllerHolder.setVibratorController(controller);
}
@@ -134,6 +136,7 @@
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 @@
+ "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 @@
public void onRequestVibrationParamsComplete(
@NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) {
Objects.requireNonNull(requestToken);
+ requireContainsNoNullElement(result);
synchronized (mLock) {
if (mVibrationParamRequest == null) {
@@ -202,6 +213,13 @@
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 @@
*
* @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 @@
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 @@
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 bf094ed..54e932a 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 @@
*/
private CompatDisplayInsets mCompatDisplayInsets;
+ private final TaskFragment.ConfigOverrideHint mResolveConfigHint;
+
private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session
@@ -819,6 +821,12 @@
@Nullable
private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+ // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+ // aspect ratio. If not null, they are used as parent container in
+ // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+ @Nullable
+ private Rect mLetterboxBoundsForAspectRatio;
+
// Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
// requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -2110,6 +2118,10 @@
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;
@@ -8436,10 +8448,15 @@
fullConfig.windowConfiguration.getRotation());
}
+ final Rect letterboxedContainerBounds =
+ mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+ ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+ : mLetterboxBoundsForAspectRatio;
// The role of CompatDisplayInsets is like the override bounds.
mCompatDisplayInsets =
new CompatDisplayInsets(
- mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
+ mDisplayContent, this, letterboxedContainerBounds,
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds);
}
private void clearSizeCompatModeAttributes() {
@@ -8511,6 +8528,7 @@
mIsAspectRatioApplied = false;
mIsEligibleForFixedOrientationLetterbox = false;
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+ mLetterboxBoundsForAspectRatio = null;
// Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
// different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -8546,13 +8564,14 @@
// 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.
+ // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
+ // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+ }
+ if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds
&& !mLetterboxUiController.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -8636,16 +8655,15 @@
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 +8671,20 @@
}
// 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 +8708,10 @@
// 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 +8724,12 @@
}
}
- /**
- * @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 +8872,7 @@
}
// 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 +8966,18 @@
* 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 +8994,9 @@
? 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 +9006,6 @@
// have the desired orientation.
final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
|| orientationWithInsets == requestedOrientation;
- if (orientationRespectedWithInsets) {
- outStableBounds.setEmpty();
- }
return orientationRespectedWithInsets;
}
@@ -9012,9 +9031,10 @@
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 +9051,10 @@
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.
@@ -9046,7 +9069,8 @@
}
final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
- if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
+ if (compatDisplayInsets != null
+ && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
// have fixed orientation letterbox again, otherwise it will show the size compat
@@ -9054,10 +9078,12 @@
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 +9160,8 @@
// 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 +9202,9 @@
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);
+ mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
}
}
@@ -9241,8 +9268,8 @@
// 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,
@@ -10741,10 +10768,10 @@
/** Whether the {@link Task} windowingMode represents a floating window*/
final boolean mIsFloating;
/**
- * Whether is letterboxed because of fixed orientation when the unresizable activity is
- * first shown.
+ * Whether is letterboxed because of fixed orientation or aspect ratio when
+ * the unresizable activity is first shown.
*/
- final boolean mIsInFixedOrientationLetterbox;
+ final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
/**
* The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
* is used to compute the appBounds.
@@ -10759,7 +10786,7 @@
/** Constructs the environment to simulate the bounds behavior of the given container. */
CompatDisplayInsets(DisplayContent display, ActivityRecord container,
- @Nullable Rect fixedOrientationBounds) {
+ @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
mOriginalRotation = display.getRotation();
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
@@ -10774,22 +10801,22 @@
mNonDecorInsets[rotation] = emptyRect;
mStableInsets[rotation] = emptyRect;
}
- mIsInFixedOrientationLetterbox = false;
+ mIsInFixedOrientationOrAspectRatioLetterbox = false;
return;
}
final Task task = container.getTask();
- mIsInFixedOrientationLetterbox = fixedOrientationBounds != null;
+ mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
// Store the bounds of the Task for the non-resizable activity to use in size compat
// mode so that the activity will not be resized regardless the windowing mode it is
// currently in.
- // When an activity needs to be letterboxed because of fixed orientation, use fixed
- // orientation bounds instead of task bounds since the activity will be displayed
- // within these even if it is in size compat mode.
- final Rect filledContainerBounds = mIsInFixedOrientationLetterbox
- ? fixedOrientationBounds
+ // When an activity needs to be letterboxed because of fixed orientation or aspect
+ // ratio, use resolved bounds instead of task bounds since the activity will be
+ // displayed within these even if it is in size compat mode.
+ final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+ ? letterboxedContainerBounds
: task != null ? task.getBounds() : display.getBounds();
final int filledContainerRotation = task != null
? task.getConfiguration().windowConfiguration.getRotation()
@@ -10811,8 +10838,13 @@
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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5e0d4f9..84dadab 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_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 @@
+ " 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 @@
*/
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 @@
*/
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 @@
? 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 @@
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 218fb7f..8042010 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 @@
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
+ static class ConfigOverrideHint {
+ @Nullable DisplayInfo mTmpOverrideDisplayInfo;
+ @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
+ boolean mUseLegacyInsetsForStableBounds;
+ }
+
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- null /* compatInsets */);
- }
-
- 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);
+ 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 @@
* 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 @@
// 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 @@
* @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 @@
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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 143605a..1496ae0 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 @@
final int mOverrideConfigTypes;
+ final int mOverrideDecorTypes;
+
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1255,10 +1257,13 @@
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 b999305f..736b051 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_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 8ca5333..da95666 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 @@
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 @@
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 cb63757..7e083ba 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_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 @@
"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 @@
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);
@@ -3957,7 +3935,7 @@
if (policy.mPasswordOwner == oldAdminUid) {
policy.mPasswordOwner = adminToTransfer.getUid();
}
-
+ transferSubscriptionOwnership(outgoingReceiver, incomingReceiver);
saveSettingsLocked(userHandle);
sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
null, null);
@@ -15913,14 +15891,6 @@
}
@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
@@ -19501,6 +19471,21 @@
.write();
}
+ private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
+ if (Flags.esimManagementEnabled()) {
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+ try {
+ subscriptionManager.setGroupOwner(subId, target.getPackageName());
+ } catch (Exception e) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
+ }
+ }
+ }
+ }
+
private void prepareTransfer(ComponentName admin, ComponentName target,
PersistableBundle bundle, int callingUserId, String adminType) {
saveTransferOwnershipBundleLocked(bundle, callingUserId);
@@ -20378,34 +20363,47 @@
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 42ac998..d02cfee 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 @@
@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 e713a82..7a9fa0f 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 @@
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 a7adc5b..a0d9be54 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.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.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.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 @@
}
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 fe7bbe0..ea08be4 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 @@
verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
verify(mMockedDisplayNotificationManager, times(2))
.onHighTemperatureExternalDisplayNotAllowed();
- verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
}
@Test
@@ -168,7 +167,6 @@
verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
verify(mMockedDisplayNotificationManager, never())
.onHighTemperatureExternalDisplayNotAllowed();
- verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
}
@Test
@@ -186,7 +184,6 @@
// 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 @@
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 040ae96..4591d91 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 @@
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 7cfc1dd..2d317af 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 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 @@
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 @@
.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 @@
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 @@
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 @@
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 @@
.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 @@
.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 @@
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 @@
.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 @@
.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 @@
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/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 7d3a110..c1f4fee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -321,8 +321,7 @@
app1PackageName); // packageName
ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
- mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
- false);
+ mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
assertEquals(list.size(), 2);
@@ -336,7 +335,7 @@
app1ProcessName, // processName
ApplicationStartInfo.START_REASON_SERVICE, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 5: Create an instance of app1 with a different user started for a broadcast
@@ -350,7 +349,7 @@
app1PackageName); // packageName
mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
- null, true /* isColdStart */);
+ buildIntent(COMPONENT), false /* isAlarm */);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -395,7 +394,7 @@
app2PackageName); // packageName
mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
- app, false);
+ app);
list.clear();
mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -409,7 +408,7 @@
app2ProcessName, // processName
ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 8: Save and load again
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index ce281da..5861917 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -112,6 +112,9 @@
@Mock
ProcessList mProcessList;
+ @Mock
+ AppStartInfoTracker mAppStartInfoTracker;
+
Context mContext;
ActivityManagerService mAms;
BroadcastConstants mConstants;
@@ -172,6 +175,8 @@
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
+
+ doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
}
public void tearDown() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 97ae0bd..13ba1e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2393,6 +2393,20 @@
assertNull(mAms.getProcessRecordLocked(PACKAGE_BLUE, getUidForPackage(PACKAGE_BLUE)));
}
+ @Test
+ public void testBroadcastAppStartInfoReported() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent timezone = new Intent(Intent.ACTION_TIME_TICK);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+ waitForIdle();
+
+ verify(mAppStartInfoTracker, times(1)).handleProcessBroadcastStart(anyLong(), any(), any(),
+ anyBoolean());
+ }
+
private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
for (int i = 0; i < r.receivers.size(); ++i) {
if (isReceiverEquals(receiver, r.receivers.get(i))) {
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 4c7a8fe..9590783 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 @@
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 f387238..12a7038 100644
--- a/services/tests/selinux/Android.bp
+++ b/services/tests/selinux/Android.bp
@@ -52,6 +52,7 @@
"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 b36c9bd..e86108d 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(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(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
- assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+ 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 @@
@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 @@
@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 @@
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 @@
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 4a70ad3..b6ccf5e 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.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 @@
// 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 @@
@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 @@
@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 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
true,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -104,7 +116,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype1",
null,
@@ -114,9 +126,9 @@
}
@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 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1", "perm2"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -140,7 +152,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm3", "perm4"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -150,11 +162,11 @@
}
@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 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -178,7 +190,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -191,7 +203,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -204,7 +216,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -214,23 +226,14 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_withCategories() {
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "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",
- new int[] {123, 456},
- "ttype",
- null,
- "tclass");
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", 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 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
null,
@@ -258,7 +261,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
null,
@@ -271,7 +274,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
new int[] {666},
@@ -284,7 +287,7 @@
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 @@
}
@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 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
new int[] {666},
@@ -324,10 +327,10 @@
}
@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 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -352,7 +355,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -362,7 +365,7 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 @@
@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 @@
anyBoolean()),
never());
- mSelinuxAutidLogsCollector.mStopRequested.set(false);
+ mSelinuxAutidLogsCollector.setStopRequested(false);
done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isTrue();
verify(
@@ -516,8 +519,8 @@
@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 0000000..2aea8a0
--- /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 5a17851..6cc650f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -26,6 +26,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static com.google.common.truth.Truth.assertThat;
@@ -68,6 +69,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -110,6 +112,7 @@
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;
@@ -222,6 +225,8 @@
mMockMagnificationConnectionManager);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
mMockFullScreenMagnificationController);
+ when(mMockMagnificationController.isFullScreenMagnificationControllerInitialized())
+ .thenReturn(true);
when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
mMockA11yController);
@@ -568,6 +573,16 @@
verify(mMockMagnificationController).setAlwaysOnMagnificationEnabled(eq(true));
}
+ @Test
+ @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
+ throws RemoteException {
+ mA11yms.setMagnificationConnection(null);
+
+ verify(mMockFullScreenMagnificationController, atLeastOnce()).reset(
+ /* displayId= */ anyInt(), /* animate= */ anyBoolean());
+ }
+
@SmallTest
@Test
public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
@@ -989,6 +1004,9 @@
@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 +1026,9 @@
@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 +1056,9 @@
@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 +1076,9 @@
@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 +1122,9 @@
@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 +1145,9 @@
@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 +1187,9 @@
@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 +1210,9 @@
@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 +1232,9 @@
@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 +1253,9 @@
@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 +1275,9 @@
@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 +1297,9 @@
@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 +1319,9 @@
@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 +1340,9 @@
@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 +1368,9 @@
@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 +1755,8 @@
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/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index f3cd0d6..7b71f85 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -20,6 +20,7 @@
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -139,6 +140,8 @@
private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
+ private boolean mMockMagnificationConnectionState;
+
FullScreenMagnificationController mFullScreenMagnificationController;
public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -175,6 +178,8 @@
mScaleProvider = new MagnificationScaleProvider(mMockContext);
+ // Assume the connection is established by default
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mMockControllerCtx,
@@ -184,7 +189,8 @@
() -> mMockThumbnail,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mMockTimeAnimator);
+ () -> mMockTimeAnimator,
+ () -> mMockMagnificationConnectionState);
}
@After
@@ -196,7 +202,6 @@
CURRENT_USER_ID);
}
-
@Test
public void testRegister_WindowManagerAndContextRegisterListeners() {
register(DISPLAY_0);
@@ -291,6 +296,21 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetScale_noConnection_doNothing() {
+ register(TEST_DISPLAY);
+
+ // Assume that the connection does not exist.
+ mMockMagnificationConnectionState = false;
+
+ final float scale = 2.0f;
+ final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ assertFalse(mFullScreenMagnificationController
+ .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+ assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
+ }
+
+ @Test
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 7fbd521..f482ddc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -197,6 +197,8 @@
private final Scroller mMockScroller = spy(new Scroller(mContext));
+ private boolean mMockMagnificationConnectionState;
+
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
private TestHandler mHandler;
@@ -229,6 +231,7 @@
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
UserHandle.USER_SYSTEM);
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mockController,
@@ -238,7 +241,8 @@
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- TimeAnimator::new) {
+ TimeAnimator::new,
+ () -> mMockMagnificationConnectionState) {
@Override
public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 58567ca..2528177 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -220,7 +220,8 @@
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mTimeAnimator));
+ () -> mTimeAnimator,
+ () -> true));
mScreenMagnificationController.register(TEST_DISPLAY);
mMagnificationConnectionManager = spy(
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 7c0dbf4..c6f3eb3 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.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 @@
@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 @@
@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 @@
@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 @@
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 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 @@
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 @@
// 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 @@
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 @@
).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 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 @@
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 @@
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 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 @@
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 @@
.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 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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 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 @@
@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 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 @@
}
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 @@
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 3f5217c..8ca8623 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 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.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 @@
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 @@
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 a606388..c17d11e 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 @@
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 2e80bc7..03302ce 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 org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -1943,8 +1942,7 @@
assertThat(mActivity.inSizeCompatMode()).isTrue();
assertActivityMaxBoundsSandboxed();
-
- final int scale = dh / dw;
+ final int scale = dh / dw;
// App bounds should be dh / scale x dw / scale
assertEquals(dw, rotatedDisplayBounds.width());
@@ -4179,13 +4177,8 @@
}
@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 +4191,58 @@
.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
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
+ setUpDisplaySizeWithApp(2200, 2280);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ final DisplayContent dc = mActivity.mDisplayContent;
+ // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+ final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+ "navbar");
+ final Binder owner = new Binder();
+ navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSize(Insets.of(0, 0, 0, 150))
+ };
+ dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+ assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo());
+ dc.sendNewConfiguration();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+ prepareMinAspectRatio(activity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_LANDSCAPE);
+ // To force config to update again but with the same landscape orientation.
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+
+ assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertNotNull(activity.getCompatDisplayInsets());
+ // Activity is not letterboxed for fixed orientation because orientation is respected
+ // with insets, and should not be in size compat mode
+ assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity.inSizeCompatMode());
}
@Test
@@ -4229,6 +4264,7 @@
// 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 1ca808f..225e85e 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 @@
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/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ba7ba532..8fe45cb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4614,6 +4614,31 @@
}
/**
+ * Set owner for this subscription.
+ *
+ * @param subscriptionId the subId of the subscription.
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setGroupOwner(int subscriptionId, @NonNull String groupOwner) {
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.setGroupOwner(subscriptionId, groupOwner);
+ } else {
+ throw new IllegalStateException("[setGroupOwner]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Set userHandle for a subscription.
*
* Used to set an association between a subscription and a user on the device so that voice
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 6678f40..1bfec29 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -309,6 +309,18 @@
*/
int setUsageSetting(int usageSetting, int subId, String callingPackage);
+ /**
+ * Set owner for this subscription.
+ *
+ * @param subId the unique SubscriptionInfo index in database
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ void setGroupOwner(int subId, String groupOwner);
+
/**
* Set userHandle for this subscription.
*
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 1fdf97a..093923f 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.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 b23a87e..fa352cf 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>
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 6189fb0..edad678 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -107,7 +107,6 @@
@Mock protected Context mContext;
@Mock protected Network mNetwork;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
@Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock protected ConnectivityManager mConnectivityManager;
@Mock protected TelephonyManager mTelephonyManager;
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 45ab986..2ba5705 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -993,6 +993,16 @@
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
+ * <p>
+ * When an Access Point’s beacon or probe response includes a Multi-BSSID Element, the
+ * returned scan results should include separate scan result for each BSSID within the
+ * Multi-BSSID Information Element. This includes both transmitted and non-transmitted BSSIDs.
+ * Original Multi-BSSID Element will be included in the Information Elements attached to
+ * each of the scan results.
+ * Note: This is the expected behavior for devices supporting 11ax (WiFi-6) and above, and an
+ * optional requirement for devices running with older WiFi generations.
+ * </p>
+ *
* @param ifaceName Name of the interface.
* @param scanType The type of scan result to be returned, can be
* {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.