From b4a4d81441ca3d8ca40b45d9419e540de0169618 Mon Sep 17 00:00:00 2001 From: Nergi Rahardi Date: Wed, 12 Jun 2024 17:27:24 +0900 Subject: [PATCH 001/509] Fix bounds clipping to be based on app bounds When PopupWindow location in LocalFloatingToolbarPopup is set to 0, it will do 0-clipping so that the coords never go negative. However, PopupWindow is positioned relative to the ViewParent Window, and it's possible that this Window's bounds are not set at (0, 0). For example, in Unity apps, there's a separate Window just to host EditText). Please take a look at this screenshot: https://screenshot.googleplex.com/4koDnS4VvXbEuxE.png Dumpsys: https://paste.googleplex.com/4609343505039360 In this case, even though the coords set is negative, it should be acceptable as Window bounds (e.g. y=1000) + negative coords (-200), will still show PopupWindow at y=800 for example. Hence, the idea of this fix here is to clip only when Window's bounds (where PopupWindow is anchored) + relative PopupWindow coords is < appBounds. Sample recordings in Pixel7a: - Overlapping with text: http://shortn/_cxVdVlvbNF - Popup hidden: http://shortn/_QnQzQ1GEG5 - After fix: http://shortn/_UG1C6cAGcg Bug: 183233870 Test: atest CtsWidgetTestCases Change-Id: Iffb7a3657fefdf6b0d100d17078527af3efb5461 --- .../LocalFloatingToolbarPopup.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index bc729f10d74c..2d94bf9a635e 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -531,8 +531,26 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { int rootViewTopOnWindow = mTmpCoords[1]; int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow; int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow; - mCoordsOnWindow.set( - Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen)); + // In some cases, app can have specific Window for Android UI components such as EditText. + // In this case, Window bounds != App bounds. Hence, instead of ensuring non-negative + // PopupWindow coords, app bounds should be used to limit the coords. For instance, + // ____ <- | + // | | |W1 & App bounds + // |___| | + // |W2 | | W2 has smaller bounds and contain EditText where PopupWindow will be opened. + // ---- <-| + // Here, we'll open PopupWindow upwards, but as PopupWindow is anchored based on W2, it + // will have negative Y coords. This negative Y is safe to use because it's still within app + // bounds. However, if it gets out of app bounds, we should clamp it to 0. + Rect appBounds = mContext + .getResources().getConfiguration().windowConfiguration.getAppBounds(); + mCoordsOnWindow.set(x - windowLeftOnScreen, y - windowTopOnScreen); + if (rootViewLeftOnScreen + mCoordsOnWindow.x < appBounds.left) { + mCoordsOnWindow.x = 0; + } + if (rootViewTopOnScreen + mCoordsOnWindow.y < appBounds.top) { + mCoordsOnWindow.y = 0; + } } /** -- GitLab From a10648f48d6efe51fe9e2bb8b5aa5433cbc27f1d Mon Sep 17 00:00:00 2001 From: Eghosa Ewansiha-Vlachavas Date: Tue, 30 Jul 2024 16:28:00 +0000 Subject: [PATCH 002/509] Use Object.equals when comparing `lastNonFullscreenBounds` in TaskInfo Flag: NONE(bug fix) Test: Presubmit Bug: 355401868 Change-Id: I0281ede24614006ead3d7adec28c736f60edb604 --- core/java/android/app/TaskInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index c83dd65233de..f7e559400f0f 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -456,7 +456,7 @@ public class TaskInfo { && Objects.equals(topActivity, that.topActivity) && isTopActivityTransparent == that.isTopActivityTransparent && isTopActivityStyleFloating == that.isTopActivityStyleFloating - && lastNonFullscreenBounds == this.lastNonFullscreenBounds + && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds) && Objects.equals(capturedLink, that.capturedLink) && capturedLinkTimestamp == that.capturedLinkTimestamp && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo); -- GitLab From 058f75fbdeaf74a18384b994586c8b37934e12bf Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Tue, 8 Oct 2024 09:03:25 +0000 Subject: [PATCH 003/509] Fix CollapsingToolbarAppCompatActivity didn't apply Edge To Edge correctly The collapsing toolbar will clip the content to its padding. In order to fix the issue, we need to set the clipToPadding and clipChildren to false. Bug: 352238050 Change-Id: Ibde2e96493e4bf21faf15045f4d95a0f73bdccca Test: manual Flag: EXEMPT bugfix --- .../android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java index 062e9b8ed032..42ffa67d5b1e 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java @@ -17,6 +17,7 @@ package com.android.settingslib.collapsingtoolbar; import android.os.Build; +import android.view.ViewGroup; import androidx.activity.ComponentActivity; import androidx.activity.EdgeToEdge; @@ -53,6 +54,8 @@ public class EdgeToEdgeUtils { .getInsets(WindowInsetsCompat.Type.statusBars()).top; // Apply the insets paddings to the view. v.setPadding(insets.left, statusBarHeight, insets.right, insets.bottom); + ((ViewGroup)v).setClipToPadding(false); + ((ViewGroup)v).setClipChildren(false); // Return CONSUMED if you don't want the window insets to keep being // passed down to descendant views. -- GitLab From bd4bf35efb28fb7ed7ea220303b3e9f42642ad27 Mon Sep 17 00:00:00 2001 From: tanxiaoyan Date: Sat, 12 Oct 2024 10:56:17 +0800 Subject: [PATCH 004/509] Fix incorrect transaction id of Transaction#apply call stack debugging Bug:372932639 Change-Id: I34c68c3d42b8df893f10627f59638ea3650a04ed Signed-off-by: tanxiaoyan --- core/java/android/view/SurfaceControl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 505a82a1518f..884a66c477b4 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -2917,12 +2917,12 @@ public final class SurfaceControl implements Parcelable { private void apply(boolean sync, boolean oneWay) { applyResizedSurfaces(); notifyReparentedSurfaces(); - nativeApplyTransaction(mNativeObject, sync, oneWay); if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( "apply", this, null, null); } + nativeApplyTransaction(mNativeObject, sync, oneWay); } /** -- GitLab From 42c3bc39895ce2990aefb0c2ce19e9c548678517 Mon Sep 17 00:00:00 2001 From: Achim Thesmann Date: Thu, 24 Oct 2024 21:35:04 +0000 Subject: [PATCH 005/509] JavaDoc fixes Consistently apply go/java-practices/javadoc based on API review feedback. Flag: EXEMPT javadoc change only Bug: 324089586 Test: atest StrictModeTest Change-Id: I8f7c6a74d201105cc49ee875974aea0b2f283f7b --- core/java/android/os/StrictMode.java | 136 +++++++++++++-------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 60a9e053e99d..75ea3d484920 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -364,7 +364,7 @@ public final class StrictMode { public static final int NETWORK_POLICY_REJECT = 2; /** - * Detect explicit calls to {@link Runtime#gc()}. + * Detects explicit calls to {@link Runtime#gc()}. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -500,7 +500,7 @@ public final class StrictMode { private Executor mExecutor; /** - * Create a Builder that detects nothing and has no violations. (but note that {@link + * Creates a Builder that detects nothing and has no violations. (but note that {@link * #build} will default to enabling {@link #penaltyLog} if no other penalties are * specified) */ @@ -508,7 +508,7 @@ public final class StrictMode { mMask = 0; } - /** Initialize a Builder from an existing ThreadPolicy. */ + /** Initializes a Builder from an existing ThreadPolicy. */ public Builder(ThreadPolicy policy) { mMask = policy.mask; mListener = policy.mListener; @@ -516,7 +516,7 @@ public final class StrictMode { } /** - * Detect everything that's potentially suspect. + * Detects everything that's potentially suspect. * *

As of the Gingerbread release this includes network and disk operations but will * likely expand in future releases. @@ -543,52 +543,52 @@ public final class StrictMode { return this; } - /** Disable the detection of everything. */ + /** Disables the detection of everything. */ public @NonNull Builder permitAll() { return disable(DETECT_THREAD_ALL); } - /** Enable detection of network operations. */ + /** Enables detection of network operations. */ public @NonNull Builder detectNetwork() { return enable(DETECT_THREAD_NETWORK); } - /** Disable detection of network operations. */ + /** Disables detection of network operations. */ public @NonNull Builder permitNetwork() { return disable(DETECT_THREAD_NETWORK); } - /** Enable detection of disk reads. */ + /** Enables detection of disk reads. */ public @NonNull Builder detectDiskReads() { return enable(DETECT_THREAD_DISK_READ); } - /** Disable detection of disk reads. */ + /** Disables detection of disk reads. */ public @NonNull Builder permitDiskReads() { return disable(DETECT_THREAD_DISK_READ); } - /** Enable detection of slow calls. */ + /** Enables detection of slow calls. */ public @NonNull Builder detectCustomSlowCalls() { return enable(DETECT_THREAD_CUSTOM); } - /** Disable detection of slow calls. */ + /** Disables detection of slow calls. */ public @NonNull Builder permitCustomSlowCalls() { return disable(DETECT_THREAD_CUSTOM); } - /** Disable detection of mismatches between defined resource types and getter calls. */ + /** Disables detection of mismatches between defined resource types and getter calls. */ public @NonNull Builder permitResourceMismatches() { return disable(DETECT_THREAD_RESOURCE_MISMATCH); } - /** Detect unbuffered input/output operations. */ + /** Detects unbuffered input/output operations. */ public @NonNull Builder detectUnbufferedIo() { return enable(DETECT_THREAD_UNBUFFERED_IO); } - /** Disable detection of unbuffered input/output operations. */ + /** Disables detection of unbuffered input/output operations. */ public @NonNull Builder permitUnbufferedIo() { return disable(DETECT_THREAD_UNBUFFERED_IO); } @@ -609,32 +609,32 @@ public final class StrictMode { return enable(DETECT_THREAD_RESOURCE_MISMATCH); } - /** Enable detection of disk writes. */ + /** Enables detection of disk writes. */ public @NonNull Builder detectDiskWrites() { return enable(DETECT_THREAD_DISK_WRITE); } - /** Disable detection of disk writes. */ + /** Disables detection of disk writes. */ public @NonNull Builder permitDiskWrites() { return disable(DETECT_THREAD_DISK_WRITE); } /** - * Detect calls to {@link Runtime#gc()}. + * Detects calls to {@link Runtime#gc()}. */ public @NonNull Builder detectExplicitGc() { return enable(DETECT_THREAD_EXPLICIT_GC); } /** - * Disable detection of calls to {@link Runtime#gc()}. + * Disables detection of calls to {@link Runtime#gc()}. */ public @NonNull Builder permitExplicitGc() { return disable(DETECT_THREAD_EXPLICIT_GC); } /** - * Show an annoying dialog to the developer on detected violations, rate-limited to be + * Shows an annoying dialog to the developer on detected violations, rate-limited to be * only a little annoying. */ public @NonNull Builder penaltyDialog() { @@ -642,7 +642,7 @@ public final class StrictMode { } /** - * Crash the whole process on violation. This penalty runs at the end of all enabled + * Crashes the whole process on violation. This penalty runs at the end of all enabled * penalties so you'll still get see logging or other violations before the process * dies. * @@ -654,7 +654,7 @@ public final class StrictMode { } /** - * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this + * Crashes the whole process on any network usage. Unlike {@link #penaltyDeath}, this * penalty runs before anything else. You must still have called {@link * #detectNetwork} to enable this. * @@ -664,18 +664,18 @@ public final class StrictMode { return enable(PENALTY_DEATH_ON_NETWORK); } - /** Flash the screen during a violation. */ + /** Flashes the screen during a violation. */ public @NonNull Builder penaltyFlashScreen() { return enable(PENALTY_FLASH); } - /** Log detected violations to the system log. */ + /** Logs detected violations to the system log. */ public @NonNull Builder penaltyLog() { return enable(PENALTY_LOG); } /** - * Enable detected violations log a stacktrace and timing data to the {@link + * Enables detected violations log a stacktrace and timing data to the {@link * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform * integrators doing beta user field data collection. */ @@ -684,7 +684,7 @@ public final class StrictMode { } /** - * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified + * Calls #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified * executor every violation. */ public @NonNull Builder penaltyListener( @@ -714,7 +714,7 @@ public final class StrictMode { } /** - * Construct the ThreadPolicy instance. + * Constructs the ThreadPolicy instance. * *

Note: if no penalties are enabled before calling build, {@link * #penaltyLog} is implicitly set. @@ -804,7 +804,7 @@ public final class StrictMode { mMask = 0; } - /** Build upon an existing VmPolicy. */ + /** Builds upon an existing VmPolicy. */ public Builder(VmPolicy base) { mMask = base.mask; mClassInstanceLimitNeedCow = true; @@ -814,7 +814,7 @@ public final class StrictMode { } /** - * Set an upper bound on how many instances of a class can be in memory at once. Helps + * Sets an upper bound on how many instances of a class can be in memory at once. Helps * to prevent object leaks. */ public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) { @@ -837,7 +837,7 @@ public final class StrictMode { return this; } - /** Detect leaks of {@link android.app.Activity} subclasses. */ + /** Detects leaks of {@link android.app.Activity} subclasses. */ public @NonNull Builder detectActivityLeaks() { return enable(DETECT_VM_ACTIVITY_LEAKS); } @@ -851,7 +851,7 @@ public final class StrictMode { } /** - * Detect reflective usage of APIs that are not part of the public Android SDK. + * Detects reflective usage of APIs that are not part of the public Android SDK. * *

Note that any non-SDK APIs that this processes accesses before this detection is * enabled may not be detected. To ensure that all such API accesses are detected, @@ -862,7 +862,7 @@ public final class StrictMode { } /** - * Permit reflective usage of APIs that are not part of the public Android SDK. Note + * Permits reflective usage of APIs that are not part of the public Android SDK. Note * that this only affects {@code StrictMode}, the underlying runtime may * continue to restrict or warn on access to methods that are not part of the * public SDK. @@ -872,7 +872,7 @@ public final class StrictMode { } /** - * Detect everything that's potentially suspect. + * Detects everything that's potentially suspect. * *

In the Honeycomb release this includes leaks of SQLite cursors, Activities, and * other closable objects but will likely expand in future releases. @@ -923,8 +923,8 @@ public final class StrictMode { } /** - * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is - * finalized without having been closed. + * Detects when an {@link android.database.sqlite.SQLiteCursor} or other SQLite + * object is finalized without having been closed. * *

You always want to explicitly close your SQLite cursors to avoid unnecessary * database contention and temporary memory leaks. @@ -934,8 +934,8 @@ public final class StrictMode { } /** - * Detect when an {@link java.io.Closeable} or other object with an explicit termination - * method is finalized without having been closed. + * Detects when an {@link java.io.Closeable} or other object with an explicit + * termination method is finalized without having been closed. * *

You always want to explicitly close such objects to avoid unnecessary resources * leaks. @@ -945,16 +945,16 @@ public final class StrictMode { } /** - * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during - * {@link Context} teardown. + * Detects when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked + * during {@link Context} teardown. */ public @NonNull Builder detectLeakedRegistrationObjects() { return enable(DETECT_VM_REGISTRATION_LEAKS); } /** - * Detect when the calling application exposes a {@code file://} {@link android.net.Uri} - * to another app. + * Detects when the calling application exposes a {@code file://} + * {@link android.net.Uri} to another app. * *

This exposure is discouraged since the receiving app may not have access to the * shared path. For example, the receiving app may not have requested the {@link @@ -972,9 +972,9 @@ public final class StrictMode { } /** - * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This - * can help you detect places that your app is inadvertently sending cleartext data - * across the network. + * Detects any network traffic from the calling app which is not wrapped in SSL/TLS. + * This can help you detect places that your app is inadvertently sending cleartext + * data across the network. * *

Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will * block further traffic on that socket to prevent accidental data leakage, in addition @@ -991,7 +991,7 @@ public final class StrictMode { } /** - * Detect when the calling application sends a {@code content://} {@link + * Detects when the calling application sends a {@code content://} {@link * android.net.Uri} to another app without setting {@link * Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link * Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. @@ -1007,7 +1007,7 @@ public final class StrictMode { } /** - * Detect any sockets in the calling app which have not been tagged using {@link + * Detects any sockets in the calling app which have not been tagged using {@link * TrafficStats}. Tagging sockets can help you investigate network usage inside your * app, such as a narrowing down heavy usage to a specific library or component. * @@ -1027,7 +1027,7 @@ public final class StrictMode { } /** - * Detect any implicit reliance on Direct Boot automatic filtering + * Detects any implicit reliance on Direct Boot automatic filtering * of {@link PackageManager} values. Violations are only triggered * when implicit calls are made while the user is locked. *

@@ -1050,7 +1050,7 @@ public final class StrictMode { } /** - * Detect access to filesystem paths stored in credential protected + * Detects access to filesystem paths stored in credential protected * storage areas while the user is locked. *

* When a user is locked, credential protected storage is @@ -1071,7 +1071,7 @@ public final class StrictMode { } /** - * Detect attempts to invoke a method on a {@link Context} that is not suited for such + * Detects attempts to invoke a method on a {@link Context} that is not suited for such * operation. *

An example of this is trying to obtain an instance of UI service (e.g. * {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not @@ -1085,7 +1085,7 @@ public final class StrictMode { } /** - * Disable detection of incorrect context use. + * Disables detection of incorrect context use. * * @see #detectIncorrectContextUse() * @@ -1097,7 +1097,7 @@ public final class StrictMode { } /** - * Detect when your app sends an unsafe {@link Intent}. + * Detects when your app sends an unsafe {@link Intent}. *

* Violations may indicate security vulnerabilities in the design of * your app, where a malicious app could trick you into granting @@ -1138,7 +1138,7 @@ public final class StrictMode { } /** - * Permit your app to launch any {@link Intent} which originated + * Permits your app to launch any {@link Intent} which originated * from outside your app. *

* Disabling this check is strongly discouraged, as @@ -1213,13 +1213,13 @@ public final class StrictMode { return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE); } - /** Log detected violations to the system log. */ + /** Logs detected violations to the system log. */ public @NonNull Builder penaltyLog() { return enable(PENALTY_LOG); } /** - * Enable detected violations log a stacktrace and timing data to the {@link + * Enables detected violations log a stacktrace and timing data to the {@link * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform * integrators doing beta user field data collection. */ @@ -1228,7 +1228,7 @@ public final class StrictMode { } /** - * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation. + * Calls #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation. */ public @NonNull Builder penaltyListener( @NonNull Executor executor, @NonNull OnVmViolationListener listener) { @@ -1257,7 +1257,7 @@ public final class StrictMode { } /** - * Construct the VmPolicy instance. + * Constructs the VmPolicy instance. * *

Note: if no penalties are enabled before calling build, {@link * #penaltyLog} is implicitly set. @@ -1473,7 +1473,7 @@ public final class StrictMode { } /** - * Determine if the given app is "bundled" as part of the system image. These bundled apps are + * Determines if the given app is "bundled" as part of the system image. These bundled apps are * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to * chase any {@link StrictMode} regressions by enabling detection when running on {@link * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds. @@ -1511,7 +1511,7 @@ public final class StrictMode { } /** - * Initialize default {@link ThreadPolicy} for the current thread. + * Initializes default {@link ThreadPolicy} for the current thread. * * @hide */ @@ -1546,7 +1546,7 @@ public final class StrictMode { } /** - * Initialize default {@link VmPolicy} for the current VM. + * Initializes default {@link VmPolicy} for the current VM. * * @hide */ @@ -2240,7 +2240,7 @@ public final class StrictMode { } /** - * Enable the recommended StrictMode defaults, with violations just being logged. + * Enables the recommended StrictMode defaults, with violations just being logged. * *

This catches disk and network access on the main thread, as well as leaked SQLite cursors * and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link @@ -2541,7 +2541,7 @@ public final class StrictMode { private static final SparseLongArray sRealLastVmViolationTime = new SparseLongArray(); /** - * Clamp the given map by removing elements with timestamp older than the given retainSince. + * Clamps the given map by removing elements with timestamp older than the given retainSince. */ private static void clampViolationTimeMap(final @NonNull SparseLongArray violationTime, final long retainSince) { @@ -2808,7 +2808,7 @@ public final class StrictMode { }; /** - * Enter a named critical span (e.g. an animation) + * Enters a named critical span (e.g. an animation) * *

The name is an arbitary label (or tag) that will be applied to any strictmode violation * that happens while this span is active. You must call finish() on the span when done. @@ -3052,7 +3052,7 @@ public final class StrictMode { /** If this is a instance count violation, the number of instances in memory, else -1. */ public long numInstances = -1; - /** Create an instance of ViolationInfo initialized from an exception. */ + /** Creates an instance of ViolationInfo initialized from an exception. */ ViolationInfo(Violation tr, int penaltyMask) { this.mViolation = tr; this.mPenaltyMask = penaltyMask; @@ -3127,8 +3127,8 @@ public final class StrictMode { } /** - * Add a {@link Throwable} from the current process that caused the underlying violation. We - * only preserve the stack trace elements. + * Adds a {@link Throwable} from the current process that caused the underlying violation. + * We only preserve the stack trace elements. * * @hide */ @@ -3156,14 +3156,14 @@ public final class StrictMode { return result; } - /** Create an instance of ViolationInfo initialized from a Parcel. */ + /** Creates an instance of ViolationInfo initialized from a Parcel. */ @UnsupportedAppUsage public ViolationInfo(Parcel in) { this(in, false); } /** - * Create an instance of ViolationInfo initialized from a Parcel. + * Creates an instance of ViolationInfo initialized from a Parcel. * * @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty * should be removed. @@ -3199,7 +3199,7 @@ public final class StrictMode { tags = in.readStringArray(); } - /** Save a ViolationInfo instance to a parcel. */ + /** Saves a ViolationInfo instance to a parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeSerializable(mViolation); @@ -3244,7 +3244,7 @@ public final class StrictMode { } } - /** Dump a ViolationInfo instance to a Printer. */ + /** Dumps a ViolationInfo instance to a Printer. */ public void dump(Printer pw, String prefix) { pw.println(prefix + "stackTrace: " + getStackTrace()); pw.println(prefix + "penalty: " + mPenaltyMask); -- GitLab From 6d1ca27a8adb34fdd91b4900ed6bc3a2d3221913 Mon Sep 17 00:00:00 2001 From: Ikram Gabiyev Date: Mon, 28 Oct 2024 13:18:18 -0700 Subject: [PATCH 006/509] Add OWNERS to shared pip package in Shell Add the PiP team as owners for the shared PiP package in WMShell. Bug: N/A Flag: EXEMPT owners change Test: N/A Change-Id: If0452bff3f7e6a845ada86827bbc6528b8fc2209 --- .../Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS new file mode 100644 index 000000000000..20d5c33dc8bf --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS @@ -0,0 +1,4 @@ +# WM shell sub-module PiP owner +hwwang@google.com +gabiyev@google.com +wuperry@google.com -- GitLab From f4172d5a7bc5c4f631b3ecfb3eda3ac7f43b6e9c Mon Sep 17 00:00:00 2001 From: Sukesh Ram Date: Mon, 28 Oct 2024 22:56:35 +0000 Subject: [PATCH 007/509] [Connected Displays in Taskbar] Refactor TaskbarDelegate for Display Signals Refactor the TaskbarDelegate to support multiple pass down displayId specific signals down to the taskbar level. Flag: EXEMPT not adding new behavior Bug: 376128251 Test: Manual Change-Id: I41ec5c394adb2e804aedf189c8f57fd2c8d84366 --- .../shared/recents/IOverviewProxy.aidl | 8 +++--- .../NavigationBarControllerImpl.java | 8 +++--- .../navigationbar/TaskbarDelegate.java | 28 +++++++++++++------ 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 283e4556d05c..83ca496dbef2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -115,23 +115,23 @@ oneway interface IOverviewProxy { /** * Sent when {@link TaskbarDelegate#checkNavBarModes} is called. */ - void checkNavBarModes() = 30; + void checkNavBarModes(int displayId) = 30; /** * Sent when {@link TaskbarDelegate#finishBarAnimations} is called. */ - void finishBarAnimations() = 31; + void finishBarAnimations(int displayId) = 31; /** * Sent when {@link TaskbarDelegate#touchAutoDim} is called. {@param reset} is true, when auto * dim is reset after a timeout. */ - void touchAutoDim(boolean reset) = 32; + void touchAutoDim(int displayid, boolean reset) = 32; /** * Sent when {@link TaskbarDelegate#transitionTo} is called. */ - void transitionTo(int barMode, boolean animate) = 33; + void transitionTo(int displayId, int barMode, boolean animate) = 33; /** * Sent when {@link TaskbarDelegate#appTransitionPending} is called. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 5e8c2c9844ee..dbee19de17df 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -395,7 +395,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.checkNavBarModes(); } else { - mTaskbarDelegate.checkNavBarModes(); + mTaskbarDelegate.checkNavBarModes(displayId); } } @@ -405,7 +405,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.finishBarAnimations(); } else { - mTaskbarDelegate.finishBarAnimations(); + mTaskbarDelegate.finishBarAnimations(displayId); } } @@ -415,7 +415,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.touchAutoDim(); } else { - mTaskbarDelegate.touchAutoDim(); + mTaskbarDelegate.touchAutoDim(displayId); } } @@ -425,7 +425,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.transitionTo(barMode, animate); } else { - mTaskbarDelegate.transitionTo(barMode, animate); + mTaskbarDelegate.transitionTo(displayId, barMode, animate); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 1216a8879751..2a3aeae2a550 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -159,7 +159,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() { @Override public void synchronizeState() { - checkNavBarModes(); + checkNavBarModes(mDisplayId); } @Override @@ -220,6 +220,16 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler(); } + @Override + public void onDisplayReady(int displayId) { + CommandQueue.Callbacks.super.onDisplayReady(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + CommandQueue.Callbacks.super.onDisplayRemoved(displayId); + } + // Separated into a method to keep setDependencies() clean/readable. private LightBarTransitionsController createLightBarTransitionsController() { @@ -349,31 +359,31 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } } - void checkNavBarModes() { + void checkNavBarModes(int displayId) { if (mOverviewProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().checkNavBarModes(); + mOverviewProxyService.getProxy().checkNavBarModes(displayId); } catch (RemoteException e) { Log.e(TAG, "checkNavBarModes() failed", e); } } - void finishBarAnimations() { + void finishBarAnimations(int displayId) { if (mOverviewProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().finishBarAnimations(); + mOverviewProxyService.getProxy().finishBarAnimations(displayId); } catch (RemoteException e) { Log.e(TAG, "finishBarAnimations() failed", e); } } - void touchAutoDim() { + void touchAutoDim(int displayId) { if (mOverviewProxyService.getProxy() == null) { return; } @@ -382,19 +392,19 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, int state = mStatusBarStateController.getState(); boolean shouldReset = state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED; - mOverviewProxyService.getProxy().touchAutoDim(shouldReset); + mOverviewProxyService.getProxy().touchAutoDim(displayId, shouldReset); } catch (RemoteException e) { Log.e(TAG, "touchAutoDim() failed", e); } } - void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) { + void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode, boolean animate) { if (mOverviewProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().transitionTo(barMode, animate); + mOverviewProxyService.getProxy().transitionTo(displayId, barMode, animate); } catch (RemoteException e) { Log.e(TAG, "transitionTo() failed, barMode: " + barMode, e); } -- GitLab From 68d6fd5a9cdb72b9984bfb7f38cf669815639923 Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Mon, 28 Oct 2024 22:52:58 +0000 Subject: [PATCH 008/509] Remove SerialService JNI layer This JNI layer is only used to proxy a basic `open` syscall, which we can now access directly from `Os`. Simplify the implementation to use this directly, preserving null return semantics for the ParcelFileDescriptor when the load fails. Bug: 375264322 Test: SerialChat + presubmit Flag: EXEMPT refactor Change-Id: I35296ea33d04ac1372dc8890fc2c6b58cbe7bb28 --- .../com/android/server/SerialService.java | 21 ++++- services/core/jni/Android.bp | 1 - .../jni/com_android_server_SerialService.cpp | 83 ------------------- services/core/jni/onload.cpp | 2 - 4 files changed, 19 insertions(+), 88 deletions(-) delete mode 100644 services/core/jni/com_android_server_SerialService.cpp diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java index 82c2038d8011..71885fdc9c81 100644 --- a/services/core/java/com/android/server/SerialService.java +++ b/services/core/java/com/android/server/SerialService.java @@ -18,22 +18,30 @@ package com.android.server; import android.annotation.EnforcePermission; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.hardware.ISerialManager; import android.hardware.SerialManagerInternal; import android.os.ParcelFileDescriptor; import android.os.PermissionEnforcer; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.io.File; +import java.io.FileDescriptor; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.function.Supplier; @android.ravenwood.annotation.RavenwoodKeepWholeClass public class SerialService extends ISerialManager.Stub { + private static final String TAG = "SerialService"; + private final Context mContext; @GuardedBy("mSerialPorts") @@ -50,7 +58,7 @@ public class SerialService extends ISerialManager.Stub { final String[] serialPorts = getSerialPorts(context); for (String serialPort : serialPorts) { mSerialPorts.put(serialPort, () -> { - return native_open(serialPort); + return tryOpen(serialPort); }); } } @@ -135,5 +143,14 @@ public class SerialService extends ISerialManager.Stub { } }; - private native ParcelFileDescriptor native_open(String path); + private static @Nullable ParcelFileDescriptor tryOpen(String path) { + try { + FileDescriptor fd = Os.open(path, OsConstants.O_RDWR | OsConstants.O_NOCTTY, 0); + return new ParcelFileDescriptor(fd); + } catch (ErrnoException e) { + Slog.e(TAG, "Could not open: " + path, e); + // We return null to preserve API semantics from earlier implementation variants. + return null; + } + } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 7a710dc51004..662f916d8a69 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -55,7 +55,6 @@ cc_library_static { "com_android_server_powerstats_PowerStatsService.cpp", "com_android_server_power_stats_CpuPowerStatsCollector.cpp", "com_android_server_hint_HintManagerService.cpp", - "com_android_server_SerialService.cpp", "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp", "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp", "com_android_server_stats_pull_StatsPullAtomService.cpp", diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp deleted file mode 100644 index 6600c981b68d..000000000000 --- a/services/core/jni/com_android_server_SerialService.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2011 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. - */ - -#define LOG_TAG "SerialServiceJNI" -#include "utils/Log.h" - -#include "jni.h" -#include -#include "android_runtime/AndroidRuntime.h" - -#include -#include -#include - -namespace android -{ - -static struct parcel_file_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; -} gParcelFileDescriptorOffsets; - -static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path) -{ - const char *pathStr = env->GetStringUTFChars(path, NULL); - - int fd = open(pathStr, O_RDWR | O_NOCTTY); - if (fd < 0) { - ALOGE("could not open %s", pathStr); - env->ReleaseStringUTFChars(path, pathStr); - return NULL; - } - env->ReleaseStringUTFChars(path, pathStr); - - jobject fileDescriptor = jniCreateFileDescriptor(env, fd); - if (fileDescriptor == NULL) { - close(fd); - return NULL; - } - return env->NewObject(gParcelFileDescriptorOffsets.mClass, - gParcelFileDescriptorOffsets.mConstructor, fileDescriptor); -} - - -static const JNINativeMethod method_table[] = { - { "native_open", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", - (void*)android_server_SerialService_open }, -}; - -int register_android_server_SerialService(JNIEnv *env) -{ - jclass clazz = env->FindClass("com/android/server/SerialService"); - if (clazz == NULL) { - ALOGE("Can't find com/android/server/SerialService"); - return -1; - } - - clazz = env->FindClass("android/os/ParcelFileDescriptor"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); - gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "", "(Ljava/io/FileDescriptor;)V"); - LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL, - "Unable to find constructor for android.os.ParcelFileDescriptor"); - - return jniRegisterNativeMethods(env, "com/android/server/SerialService", - method_table, NELEM(method_table)); -} - -}; diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 6464081d615a..f9633e2e0b23 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -33,7 +33,6 @@ int register_android_server_PowerStatsService(JNIEnv* env); int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env); int register_android_server_HintManagerService(JNIEnv* env); int register_android_server_storage_AppFuse(JNIEnv* env); -int register_android_server_SerialService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); int register_android_server_UsbAlsaJackDetector(JNIEnv* env); int register_android_server_UsbAlsaMidiDevice(JNIEnv* env); @@ -93,7 +92,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_PowerStatsService(env); register_android_server_power_stats_CpuPowerStatsCollector(env); register_android_server_HintManagerService(env); - register_android_server_SerialService(env); register_android_server_InputManager(env); register_android_server_LightsService(env); register_android_server_UsbDeviceManager(env); -- GitLab From e18322e5ddfa88c914b6495714b77375363f1252 Mon Sep 17 00:00:00 2001 From: mattsziklay Date: Mon, 28 Oct 2024 19:03:16 -0700 Subject: [PATCH 009/509] Remove moveToSplit Removes the moveToSplit function and the SplitScreenListener that calls it. Use of this listener causes bugs and the cause of the original issue has been corrected on Launcher side. Bug: 338382550 Test: manual Flag: EXEMPT bugfix Change-Id: I5e4b03296775e263f2ad48acafb26903eccd1aa3 --- .../wm/shell/desktopmode/DesktopTasksController.kt | 13 ------------- .../DesktopModeWindowDecorViewModel.java | 14 -------------- 2 files changed, 27 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index eec2ba5f41cd..5f56f0099a58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -550,19 +550,6 @@ class DesktopTasksController( } } - /** Move a desktop app to split screen. */ - fun moveToSplit(task: RunningTaskInfo) { - logV( "moveToSplit taskId=%s", task.taskId) - desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId) - val wct = WindowContainerTransaction() - wct.setBounds(task.token, Rect()) - // Rather than set windowing mode to multi-window at task level, set it to - // undefined and inherit from split stage. - wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) - - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) - } - private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) { if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) { splitScreenController.prepareExitSplitScreen( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index a3324cc6f286..a5c9b2dec54d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -41,7 +41,6 @@ import static com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import android.annotation.NonNull; import android.app.ActivityManager; @@ -120,8 +119,6 @@ import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.splitscreen.SplitScreen; -import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -445,17 +442,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, @Override public void setSplitScreenController(SplitScreenController splitScreenController) { mSplitScreenController = splitScreenController; - mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() { - @Override - public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) { - if (visible && stage != STAGE_TYPE_UNDEFINED) { - DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId); - if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopTasksController.moveToSplit(decor.mTaskInfo); - } - } - } - }); } @Override -- GitLab From 93f84e5af9e0bb51f0f3cb3573e585ef01408933 Mon Sep 17 00:00:00 2001 From: Willie Koomson Date: Mon, 15 Jul 2024 19:52:42 +0000 Subject: [PATCH 010/509] Save generated previews in AppWidgetService This change updates AppWidgetServiceImpl to persist generated previews in /data/system_ce//appwidget/previews. Each file contains the previews for a single provider, written as a GeneratedPreviewsProto message. Previews are cleared when a provider app is updated, deleted (PACKAGE_REMOVED) or its data is cleared (PACKAGE_DATA_CLEARED). Also updates the bug for the feature flag to the correct number. Test: Manual, set previews and reboot, then clear data and remove package. Test: AppWidgetTest#testGeneratedPreviewPersistence Bug: 364420494 Flag: android.appwidget.flags.remote_views_proto Change-Id: I92e115602202efd1c2964364bc3ef32fda0ae472 --- core/java/android/appwidget/flags.aconfig | 2 +- core/proto/android/service/appwidget.proto | 13 + .../appwidget/AppWidgetServiceImpl.java | 440 +++++++++++++++++- 3 files changed, 439 insertions(+), 16 deletions(-) diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 3839b5fa2599..e5c94fccfe28 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -55,7 +55,7 @@ flag { name: "remote_views_proto" namespace: "app_widgets" description: "Enable support for persisting RemoteViews previews to Protobuf" - bug: "306546610" + bug: "364420494" } flag { diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto index 97350ef90eec..fb907196bfc7 100644 --- a/core/proto/android/service/appwidget.proto +++ b/core/proto/android/service/appwidget.proto @@ -20,6 +20,8 @@ package android.service.appwidget; option java_multiple_files = true; option java_outer_classname = "AppWidgetServiceProto"; +import "frameworks/base/core/proto/android/widget/remoteviews.proto"; + // represents the object holding the dump info of the app widget service message AppWidgetServiceDumpProto { repeated WidgetProto widgets = 1; // the array of bound widgets @@ -38,3 +40,14 @@ message WidgetProto { optional int32 maxHeight = 9; optional bool restoreCompleted = 10; } + +// represents a set of widget previews for a particular provider +message GeneratedPreviewsProto { + repeated Preview previews = 1; + + // represents a particular RemoteViews preview, which may be set for multiple categories + message Preview { + repeated int32 widget_categories = 1; + optional android.widget.RemoteViewsProto views = 2; + } +} \ No newline at end of file diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index f9abd8558ca8..7ebbac41240c 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.appwidget; import static android.appwidget.flags.Flags.remoteAdapterConversion; +import static android.appwidget.flags.Flags.remoteViewsProto; import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath; import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers; import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot; @@ -31,6 +32,7 @@ import static com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.PermissionName; @@ -104,6 +106,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.service.appwidget.AppWidgetServiceDumpProto; +import android.service.appwidget.GeneratedPreviewsProto; import android.service.appwidget.WidgetProto; import android.text.TextUtils; import android.util.ArrayMap; @@ -122,7 +125,9 @@ import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TypedValue; import android.util.Xml; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoUtils; import android.view.Display; import android.view.View; import android.widget.RemoteViews; @@ -134,6 +139,7 @@ import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; +import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; @@ -221,6 +227,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // XML attribute for widget ids that are pending deletion. // See {@link Provider#pendingDeletedWidgetIds}. private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids"; + // Name of service directory in /data/system_ce// + private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget"; + // Name of previews directory in /data/system_ce//appwidget/ + private static final String WIDGET_PREVIEWS_DIRNAME = "previews"; // Hard limit of number of hosts an app can create, note that the app that hosts the widgets // can have multiple instances of {@link AppWidgetHost}, typically in respect to different @@ -320,6 +330,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Handler to the background thread that saves states to disk. private Handler mSaveStateHandler; + // Handler to the background thread that saves generated previews to disk. All operations that + // modify saved previews must be run on this Handler. + private Handler mSavePreviewsHandler; // Handler to the foreground thread that handles broadcasts related to user // and package events, as well as various internal events within // AppWidgetService. @@ -363,6 +376,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } else { mSaveStateHandler = BackgroundThread.getHandler(); } + mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper()); final ServiceThread serviceThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */); serviceThread.start(); @@ -382,7 +396,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS, DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS); mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval, - generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders); + generatedPreviewMaxCallsPerInterval, + // Set a limit on the number of providers if storing them in memory. + remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange); @@ -648,7 +664,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int i = 0; i < providerCount; i++) { Provider provider = mProviders.get(i); if (provider.id.uid == clearedUid) { - changed |= provider.clearGeneratedPreviewsLocked(); + if (remoteViewsProto()) { + changed |= clearGeneratedPreviewsAsync(provider); + } else { + changed |= provider.clearGeneratedPreviewsLocked(); + } + if (DEBUG) { + Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed); + } } } return changed; @@ -3250,6 +3273,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku deleteWidgetsLocked(provider, UserHandle.USER_ALL); mProviders.remove(provider); mGeneratedPreviewsApiCounter.remove(provider.id); + if (remoteViewsProto()) { + clearGeneratedPreviewsAsync(provider); + } // no need to send the DISABLE broadcast, since the receiver is gone anyway cancelBroadcastsLocked(provider); @@ -3828,6 +3854,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } catch (IOException e) { Slog.w(TAG, "Failed to read state: " + e); } + + if (remoteViewsProto()) { + try { + loadGeneratedPreviewCategoriesLocked(profileId); + } catch (IOException e) { + Slog.w(TAG, "Failed to read preview categories: " + e); + } + } } if (version >= 0) { @@ -4597,6 +4631,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku keep.add(providerId); // Use the new AppWidgetProviderInfo. provider.setPartialInfoLocked(info); + // Clear old previews + if (remoteViewsProto()) { + clearGeneratedPreviewsAsync(provider); + } else { + provider.clearGeneratedPreviewsLocked(); + } // If it's enabled final int M = provider.widgets.size(); if (M > 0) { @@ -4888,6 +4928,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mSecurityPolicy.enforceCallFromPackage(callingPackage); ensureWidgetCategoryCombinationIsValid(widgetCategory); + AndroidFuture result = null; synchronized (mLock) { ensureGroupStateLoadedLocked(profileId); final int providerCount = mProviders.size(); @@ -4921,10 +4962,23 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku callingPackage); if (providerIsInCallerProfile && !shouldFilterAppAccess && (providerIsInCallerPackage || hasBindAppWidgetPermission)) { - return provider.getGeneratedPreviewLocked(widgetCategory); + if (remoteViewsProto()) { + result = getGeneratedPreviewsAsync(provider, widgetCategory); + } else { + return provider.getGeneratedPreviewLocked(widgetCategory); + } } } } + + if (result != null) { + try { + return result.get(); + } catch (Exception e) { + Slog.e(TAG, "Failed to get generated previews Future result", e); + return null; + } + } // Either the provider does not exist or the caller does not have permission to access its // previews. return null; @@ -4954,8 +5008,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku providerComponent + " is not a valid AppWidget provider"); } if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) { - provider.setGeneratedPreviewLocked(widgetCategories, preview); - scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + if (remoteViewsProto()) { + setGeneratedPreviewsAsync(provider, widgetCategories, preview); + } else { + provider.setGeneratedPreviewLocked(widgetCategories, preview); + scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } return true; } return false; @@ -4983,11 +5041,361 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku throw new IllegalArgumentException( providerComponent + " is not a valid AppWidget provider"); } - final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories); - if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + + if (remoteViewsProto()) { + removeGeneratedPreviewsAsync(provider, widgetCategories); + } else { + final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories); + if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } + } + } + + /** + * Return previews for the specified provider from a background thread. The result of the future + * is nullable. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private AndroidFuture getGeneratedPreviewsAsync( + @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) { + AndroidFuture result = new AndroidFuture<>(); + mSavePreviewsHandler.post(() -> { + SparseArray previews = loadGeneratedPreviews(provider); + for (int i = 0; i < previews.size(); i++) { + if ((widgetCategory & previews.keyAt(i)) != 0) { + result.complete(previews.valueAt(i)); + return; + } + } + result.complete(null); + }); + return result; + } + + /** + * Set previews for the specified provider on a background thread. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories, + @NonNull RemoteViews preview) { + mSavePreviewsHandler.post(() -> { + SparseArray previews = loadGeneratedPreviews(provider); + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + previews.put(flag, preview); + } + } + saveGeneratedPreviews(provider, previews, /* notify= */ true); + }); + } + + /** + * Remove previews for the specified provider on a background thread. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) { + mSavePreviewsHandler.post(() -> { + SparseArray previews = loadGeneratedPreviews(provider); + boolean changed = false; + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + changed |= previews.removeReturnOld(flag) != null; + } + } + if (changed) { + saveGeneratedPreviews(provider, previews, /* notify= */ true); + } + }); + } + + /** + * Clear previews for the specified provider on a background thread. Returns true if changed + * (i.e. there are previews to clear). If returns true, the caller should schedule a providers + * changed notification. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) { + mSavePreviewsHandler.post(() -> { + saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false); + }); + return provider.info.generatedPreviewCategories != 0; + } + + private void checkSavePreviewsThread() { + if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) { + throw new IllegalStateException("Only modify previews on the background thread"); } } + /** + * Load previews from file for the given provider. If there are no previews, returns an empty + * SparseArray. Else, returns a SparseArray of the previews mapped by widget category. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private SparseArray loadGeneratedPreviews(@NonNull Provider provider) { + checkSavePreviewsThread(); + try { + AtomicFile previewsFile = getWidgetPreviewsFile(provider); + if (!previewsFile.exists()) { + return new SparseArray<>(); + } + ProtoInputStream input = new ProtoInputStream(previewsFile.readFully()); + SparseArray entries = readGeneratedPreviewsFromProto(input); + SparseArray singleCategoryKeyedEntries = new SparseArray<>(); + for (int i = 0; i < entries.size(); i++) { + int widgetCategories = entries.keyAt(i); + RemoteViews preview = entries.valueAt(i); + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + singleCategoryKeyedEntries.put(flag, preview); + } + } + } + return singleCategoryKeyedEntries; + } catch (IOException e) { + Slog.e(TAG, "Failed to load generated previews for " + provider, e); + return new SparseArray<>(); + } + } + + /** + * This is called when loading profile/group state to populate + * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved. + * + * This is the only time previews are read while not on mSavePreviewsHandler. It happens once + * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync + * happen for that profile. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @GuardedBy("mLock") + private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException { + for (Provider provider : mProviders) { + if (provider.id.getProfile().getIdentifier() != profileId) { + continue; + } + AtomicFile previewsFile = getWidgetPreviewsFile(provider); + if (!previewsFile.exists()) { + continue; + } + ProtoInputStream input = new ProtoInputStream(previewsFile.readFully()); + provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto( + input); + if (DEBUG) { + Slog.i(TAG, TextUtils.formatSimple( + "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId, + provider, provider.info.generatedPreviewCategories)); + } + } + } + + /** + * Save the given previews into storage. + * + * @param provider Provider for which to save previews + * @param previews Previews to save. If null or empty, clears any saved previews for this + * provider. + * @param notify If true, then this function will notify hosts of updated provider info. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void saveGeneratedPreviews(@NonNull Provider provider, + @Nullable SparseArray previews, boolean notify) { + checkSavePreviewsThread(); + AtomicFile file = null; + FileOutputStream stream = null; + try { + file = getWidgetPreviewsFile(provider); + if (previews == null || previews.size() == 0) { + if (file.exists()) { + if (DEBUG) { + Slog.i(TAG, "Deleting widget preview file " + file); + } + file.delete(); + } + } else { + if (DEBUG) { + Slog.i(TAG, "Writing widget preview file " + file); + } + ProtoOutputStream out = new ProtoOutputStream(); + writePreviewsToProto(out, previews); + stream = file.startWrite(); + stream.write(out.getBytes()); + file.finishWrite(stream); + } + + synchronized (mLock) { + provider.updateGeneratedPreviewCategoriesLocked(previews); + if (notify) { + scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId()); + } + } + } catch (IOException e) { + if (file != null && stream != null) { + file.failWrite(stream); + } + Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName); + } + } + + + /** + * Write the given previews as a GeneratedPreviewsProto to the output stream. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void writePreviewsToProto(@NonNull ProtoOutputStream out, + @NonNull SparseArray generatedPreviews) { + // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates. + SparseArray> previewsToWrite = new SparseArray<>(); + for (int i = 0; i < generatedPreviews.size(); i++) { + int widgetCategory = generatedPreviews.keyAt(i); + RemoteViews views = generatedPreviews.valueAt(i); + if (!previewsToWrite.contains(views.hashCode())) { + previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views)); + } else { + Pair entry = previewsToWrite.get(views.hashCode()); + previewsToWrite.put(views.hashCode(), + Pair.create(entry.first | widgetCategory, views)); + } + } + + for (int i = 0; i < previewsToWrite.size(); i++) { + final long token = out.start(GeneratedPreviewsProto.PREVIEWS); + Pair entry = previewsToWrite.valueAt(i); + out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first); + final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS); + entry.second.writePreviewToProto(mContext, out); + out.end(viewsToken); + out.end(token); + } + } + + /** + * Read a GeneratedPreviewsProto message from the input stream. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private SparseArray readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input) + throws IOException { + SparseArray entries = new SparseArray<>(); + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.PREVIEWS: + final long token = input.start(GeneratedPreviewsProto.PREVIEWS); + Pair entry = readSinglePreviewFromProto(input, + /* skipViews= */ false); + entries.put(entry.first, entry.second); + input.end(token); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return entries; + } + + /** + * Read the widget categories from GeneratedPreviewsProto and return an int representing the + * combined widget categories of all the previews. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @AppWidgetProviderInfo.CategoryFlags + private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input) + throws IOException { + int widgetCategories = 0; + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.PREVIEWS: + final long token = input.start(GeneratedPreviewsProto.PREVIEWS); + Pair entry = readSinglePreviewFromProto(input, + /* skipViews= */ true); + widgetCategories |= entry.first; + input.end(token); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return widgetCategories; + } + + /** + * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a + * pair of widget category and corresponding RemoteViews. If skipViews is true, this function + * will only read widget categories and the returned RemoteViews will be null. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private Pair readSinglePreviewFromProto(@NonNull ProtoInputStream input, + boolean skipViews) throws IOException { + int widgetCategories = 0; + RemoteViews views = null; + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.Preview.VIEWS: + if (skipViews) { + // ProtoInputStream will skip over the nested message when nextField() is + // called. + continue; + } + final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS); + try { + views = RemoteViews.createPreviewFromProto(mContext, input); + } catch (Exception e) { + Slog.e(TAG, "Unable to deserialize RemoteViews", e); + } + input.end(token); + break; + case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES: + widgetCategories = input.readInt( + GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return Pair.create(widgetCategories, views); + } + + /** + * Returns the file in which all generated previews for this provider are stored. This will be + * a path of the form: + * {@literal /data/system_ce//appwidget/previews/--.binpb} + * + * This function will not create the file if it does not already exist. + */ + @NonNull + private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException { + int userId = provider.getUserId(); + File previewsDirectory = getWidgetPreviewsDirectory(userId); + File providerPreviews = Environment.buildPath(previewsDirectory, + TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(), + provider.id.componentName.getClassName(), provider.id.uid)); + return new AtomicFile(providerPreviews); + } + + /** + * Returns the widget previews directory for the given user, creating it if it does not exist. + * This will be a path of the form: + * {@literal /data/system_ce//appwidget/previews} + */ + @NonNull + private static File getWidgetPreviewsDirectory(int userId) throws IOException { + File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId); + File previewsDirectory = Environment.buildPath(dataSystemCeDirectory, + APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME); + if (!previewsDirectory.exists()) { + if (!previewsDirectory.mkdirs()) { + throw new IOException("Unable to create widget preview directory " + + previewsDirectory.getPath()); + } + } + return previewsDirectory; + } + private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) { int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD @@ -5419,11 +5827,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); } if (newInfo != null) { + newInfo.generatedPreviewCategories = info.generatedPreviewCategories; info = newInfo; if (DEBUG) { Objects.requireNonNull(info); } - updateGeneratedPreviewCategoriesLocked(); } } mInfoParsed = true; @@ -5480,7 +5888,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku generatedPreviews.put(flag, preview); } } - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); } @GuardedBy("this.mLock") @@ -5492,7 +5900,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } if (changed) { - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); } return changed; } @@ -5501,17 +5909,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku public boolean clearGeneratedPreviewsLocked() { if (generatedPreviews.size() > 0) { generatedPreviews.clear(); - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); return true; } return false; } - @GuardedBy("this.mLock") - private void updateGeneratedPreviewCategoriesLocked() { + private void updateGeneratedPreviewCategoriesLocked( + @Nullable SparseArray previews) { info.generatedPreviewCategories = 0; - for (int i = 0; i < generatedPreviews.size(); i++) { - info.generatedPreviewCategories |= generatedPreviews.keyAt(i); + if (previews != null) { + for (int i = 0; i < previews.size(); i++) { + info.generatedPreviewCategories |= previews.keyAt(i); + } } } -- GitLab From 841d528b7d89c8c0b05d53e93e3bd3de9a340fb0 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Wed, 30 Oct 2024 07:06:01 +0000 Subject: [PATCH 011/509] Adding Dim Bounds to Proto Bug: 370958445 Test: TaskFragmentOrganizerTest Flag: TEST_ONLY Change-Id: I49763f9c1f83175290bb2dcc831b3b79924e0d98 --- core/proto/android/server/windowmanagerservice.proto | 1 + services/core/java/com/android/server/wm/WindowState.java | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 654d83c827c9..407790c89202 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -465,6 +465,7 @@ message WindowStateProto { repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46; repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47; optional int32 requested_visible_types = 48; + optional .android.graphics.RectProto dim_bounds = 49; } message IdentifierProto { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 614187682ef4..3b2349b02250 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -157,6 +157,7 @@ import static com.android.server.wm.WindowStateProto.ANIMATING_EXIT; import static com.android.server.wm.WindowStateProto.ANIMATOR; import static com.android.server.wm.WindowStateProto.ATTRIBUTES; import static com.android.server.wm.WindowStateProto.DESTROYING; +import static com.android.server.wm.WindowStateProto.DIM_BOUNDS; import static com.android.server.wm.WindowStateProto.DISPLAY_ID; import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION; import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS; @@ -4115,6 +4116,12 @@ class WindowState extends WindowContainer implements WindowManagerP mMergedLocalInsetsSources.valueAt(i).dumpDebug(proto, MERGED_LOCAL_INSETS_SOURCES); } } + if (getDimController() != null) { + final Rect dimBounds = getDimController().getDimBounds(); + if (dimBounds != null) { + dimBounds.dumpDebug(proto, DIM_BOUNDS); + } + } proto.end(token); } -- GitLab From 99fa3031e2e0fc73215c5df899541f5cb7d3a88c Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Wed, 30 Oct 2024 07:33:36 +0000 Subject: [PATCH 012/509] Support SurfaceControlRegistry logs in native Log state changes in native when a transaction is merged or applied. Bug: b/366484871 Test: adb logcat to check for logs Flag: EXEMPT logging Change-Id: Ie61c2655609f4513291d55a7a82570e392086fb8 --- core/java/android/view/SurfaceControl.java | 3 ++- core/jni/android_view_SurfaceControl.cpp | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 94f415b8680f..7fbc701df988 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -311,6 +311,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeNotifyShutdown(); private static native void nativeSetLuts(long transactionObj, long nativeObject, float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys); + private static native void nativeEnableDebugLogCallPoints(long transactionObj); /** * Transforms that can be applied to buffers as they are displayed to a window. @@ -4552,7 +4553,6 @@ public final class SurfaceControl implements Parcelable { } /** - * TODO(b/366484871): To be removed once we have some logging in native * This is called when BlastBufferQueue.mergeWithNextTransaction() is called from java, and * for the purposes of logging that path. */ @@ -4563,6 +4563,7 @@ public final class SurfaceControl implements Parcelable { if (mCalls != null) { mCalls.clear(); } + nativeEnableDebugLogCallPoints(mNativeObject); } } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index f162b7410b10..1285cc359169 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -2380,6 +2380,11 @@ SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSur } } +static void nativeEnableDebugLogCallPoints(JNIEnv* env, jclass clazz, jlong transactionObj) { + auto transaction = reinterpret_cast(transactionObj); + transaction->enableDebugLogCallPoints(); +} + static const JNINativeMethod sSurfaceControlMethods[] = { // clang-format off {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", @@ -2626,6 +2631,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeNotifyShutdown", "()V", (void*)nativeNotifyShutdown }, {"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts }, + {"nativeEnableDebugLogCallPoints", "(J)V", (void*)nativeEnableDebugLogCallPoints }, // clang-format on }; -- GitLab From 3da32b37d5728ba25235a066ad51877439e815c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Yavero=C4=9Flu?= Date: Wed, 30 Oct 2024 10:19:26 +0000 Subject: [PATCH 013/509] Remove unused code from the integrity service serializer and parser. Change-Id: Ic1a44a1fbb3fb15811da2339d96e7f02c1925ff9 --- .../integrity/parser/RuleMetadataParser.java | 66 -- .../serializer/RuleBinarySerializer.java | 324 ------- .../serializer/RuleIndexingDetails.java | 69 -- .../RuleIndexingDetailsIdentifier.java | 151 --- .../serializer/RuleMetadataSerializer.java | 52 - .../serializer/RuleSerializeException.java | 32 - .../integrity/serializer/RuleSerializer.java | 39 - .../serializer/RuleBinarySerializerTest.java | 914 ------------------ .../RuleIndexingDetailsIdentifierTest.java | 344 ------- 9 files changed, 1991 deletions(-) delete mode 100644 services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java delete mode 100644 services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java delete mode 100644 services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java delete mode 100644 services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java delete mode 100644 services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java delete mode 100644 services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java delete mode 100644 services/core/java/com/android/server/integrity/serializer/RuleSerializer.java delete mode 100644 services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java deleted file mode 100644 index e831e40e70d1..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.parser; - -import android.annotation.Nullable; -import android.util.Xml; - -import com.android.modules.utils.TypedXmlPullParser; -import com.android.server.integrity.model.RuleMetadata; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; - -/** Helper class for parsing rule metadata. */ -public class RuleMetadataParser { - - public static final String RULE_PROVIDER_TAG = "P"; - public static final String VERSION_TAG = "V"; - - /** Parse the rule metadata from an input stream. */ - @Nullable - public static RuleMetadata parse(InputStream inputStream) - throws XmlPullParserException, IOException { - - String ruleProvider = ""; - String version = ""; - - TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream); - - int eventType; - while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tag = xmlPullParser.getName(); - switch (tag) { - case RULE_PROVIDER_TAG: - ruleProvider = xmlPullParser.nextText(); - break; - case VERSION_TAG: - version = xmlPullParser.nextText(); - break; - default: - throw new IllegalStateException("Unknown tag in metadata: " + tag); - } - } - } - - return new RuleMetadata(ruleProvider, version); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java deleted file mode 100644 index 8ba5870aef0f..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; -import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; -import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; -import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START; -import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; -import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; -import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; -import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; -import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.InstallerAllowedByManifestFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.IntegrityUtils; -import android.content.integrity.Rule; - -import com.android.internal.util.Preconditions; -import com.android.server.integrity.model.BitOutputStream; -import com.android.server.integrity.model.ByteTrackedOutputStream; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */ -public class RuleBinarySerializer implements RuleSerializer { - static final int TOTAL_RULE_SIZE_LIMIT = 200000; - static final int INDEXED_RULE_SIZE_LIMIT = 100000; - static final int NONINDEXED_RULE_SIZE_LIMIT = 1000; - - // Get the byte representation for a list of rules. - @Override - public byte[] serialize(List rules, Optional formatVersion) - throws RuleSerializeException { - try { - ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream(); - serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream()); - return rulesOutputStream.toByteArray(); - } catch (Exception e) { - throw new RuleSerializeException(e.getMessage(), e); - } - } - - // Get the byte representation for a list of rules, and write them to an output stream. - @Override - public void serialize( - List rules, - Optional formatVersion, - OutputStream rulesFileOutputStream, - OutputStream indexingFileOutputStream) - throws RuleSerializeException { - try { - if (rules == null) { - throw new IllegalArgumentException("Null rules cannot be serialized."); - } - - if (rules.size() > TOTAL_RULE_SIZE_LIMIT) { - throw new IllegalArgumentException("Too many rules provided: " + rules.size()); - } - - // Determine the indexing groups and the order of the rules within each indexed group. - Map>> indexedRules = - RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); - - // Validate the rule blocks are not larger than expected limits. - verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT); - verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT); - verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT); - - // Serialize the rules. - ByteTrackedOutputStream ruleFileByteTrackedOutputStream = - new ByteTrackedOutputStream(rulesFileOutputStream); - serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream); - LinkedHashMap packageNameIndexes = - serializeRuleList( - indexedRules.get(PACKAGE_NAME_INDEXED), - ruleFileByteTrackedOutputStream); - LinkedHashMap appCertificateIndexes = - serializeRuleList( - indexedRules.get(APP_CERTIFICATE_INDEXED), - ruleFileByteTrackedOutputStream); - LinkedHashMap unindexedRulesIndexes = - serializeRuleList( - indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream); - - // Serialize their indexes. - BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream); - serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true); - serializeIndexGroup( - appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true); - serializeIndexGroup( - unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false); - indexingBitOutputStream.flush(); - } catch (Exception e) { - throw new RuleSerializeException(e.getMessage(), e); - } - } - - private void verifySize(Map> ruleListMap, int ruleSizeLimit) { - int totalRuleCount = - ruleListMap.values().stream() - .map(list -> list.size()) - .collect(Collectors.summingInt(Integer::intValue)); - if (totalRuleCount > ruleSizeLimit) { - throw new IllegalArgumentException( - "Too many rules provided in the indexing group. Provided " - + totalRuleCount - + " limit " - + ruleSizeLimit); - } - } - - private void serializeRuleFileMetadata( - Optional formatVersion, ByteTrackedOutputStream outputStream) - throws IOException { - int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION); - - BitOutputStream bitOutputStream = new BitOutputStream(outputStream); - bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue); - bitOutputStream.flush(); - } - - private LinkedHashMap serializeRuleList( - Map> rulesMap, ByteTrackedOutputStream outputStream) - throws IOException { - Preconditions.checkArgument( - rulesMap != null, "serializeRuleList should never be called with null rule list."); - - BitOutputStream bitOutputStream = new BitOutputStream(outputStream); - LinkedHashMap indexMapping = new LinkedHashMap(); - indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount()); - - List sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList()); - int indexTracker = 0; - for (String key : sortedKeys) { - if (indexTracker >= INDEXING_BLOCK_SIZE) { - indexMapping.put(key, outputStream.getWrittenBytesCount()); - indexTracker = 0; - } - - for (Rule rule : rulesMap.get(key)) { - serializeRule(rule, bitOutputStream); - bitOutputStream.flush(); - indexTracker++; - } - } - indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount()); - - return indexMapping; - } - - private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException { - if (rule == null) { - throw new IllegalArgumentException("Null rule can not be serialized"); - } - - // Start with a '1' bit to mark the start of a rule. - bitOutputStream.setNext(); - - serializeFormula(rule.getFormula(), bitOutputStream); - bitOutputStream.setNext(EFFECT_BITS, rule.getEffect()); - - // End with a '1' bit to mark the end of a rule. - bitOutputStream.setNext(); - } - - private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream) - throws IOException { - if (formula instanceof AtomicFormula) { - serializeAtomicFormula((AtomicFormula) formula, bitOutputStream); - } else if (formula instanceof CompoundFormula) { - serializeCompoundFormula((CompoundFormula) formula, bitOutputStream); - } else if (formula instanceof InstallerAllowedByManifestFormula) { - bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START); - } else { - throw new IllegalArgumentException( - String.format("Invalid formula type: %s", formula.getClass())); - } - } - - private void serializeCompoundFormula( - CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException { - if (compoundFormula == null) { - throw new IllegalArgumentException("Null compound formula can not be serialized"); - } - - bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START); - bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector()); - for (IntegrityFormula formula : compoundFormula.getFormulas()) { - serializeFormula(formula, bitOutputStream); - } - bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END); - } - - private void serializeAtomicFormula( - AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException { - if (atomicFormula == null) { - throw new IllegalArgumentException("Null atomic formula can not be serialized"); - } - - bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START); - bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey()); - if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) { - AtomicFormula.StringAtomicFormula stringAtomicFormula = - (AtomicFormula.StringAtomicFormula) atomicFormula; - bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ); - serializeStringValue( - stringAtomicFormula.getValue(), - stringAtomicFormula.getIsHashedValue(), - bitOutputStream); - } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) { - AtomicFormula.LongAtomicFormula longAtomicFormula = - (AtomicFormula.LongAtomicFormula) atomicFormula; - bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator()); - // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream - long value = longAtomicFormula.getValue(); - serializeIntValue((int) (value >>> 32), bitOutputStream); - serializeIntValue((int) value, bitOutputStream); - } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) { - AtomicFormula.BooleanAtomicFormula booleanAtomicFormula = - (AtomicFormula.BooleanAtomicFormula) atomicFormula; - bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ); - serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream); - } else { - throw new IllegalArgumentException( - String.format("Invalid atomic formula type: %s", atomicFormula.getClass())); - } - } - - private void serializeIndexGroup( - LinkedHashMap indexes, - BitOutputStream bitOutputStream, - boolean isIndexed) - throws IOException { - // Output the starting location of this indexing group. - serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream); - serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream); - - // If the group is indexed, output the locations of the indexes. - if (isIndexed) { - for (Map.Entry entry : indexes.entrySet()) { - if (!entry.getKey().equals(START_INDEXING_KEY) - && !entry.getKey().equals(END_INDEXING_KEY)) { - serializeStringValue( - entry.getKey(), /* isHashedValue= */ false, bitOutputStream); - serializeIntValue(entry.getValue(), bitOutputStream); - } - } - } - - // Output the end location of this indexing group. - serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream); - serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream); - } - - private void serializeStringValue( - String value, boolean isHashedValue, BitOutputStream bitOutputStream) - throws IOException { - if (value == null) { - throw new IllegalArgumentException("String value can not be null."); - } - byte[] valueBytes = getBytesForString(value, isHashedValue); - - bitOutputStream.setNext(isHashedValue); - bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length); - for (byte valueByte : valueBytes) { - bitOutputStream.setNext(/* numOfBits= */ 8, valueByte); - } - } - - private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException { - bitOutputStream.setNext(/* numOfBits= */ 32, value); - } - - private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) - throws IOException { - bitOutputStream.setNext(value); - } - - // Get the byte array for a value. - // If the value is not hashed, use its byte array form directly. - // If the value is hashed, get the raw form decoding of the value. All hashed values are - // hex-encoded. Serialized values are in raw form. - private static byte[] getBytesForString(String value, boolean isHashedValue) { - if (!isHashedValue) { - return value.getBytes(StandardCharsets.UTF_8); - } - return IntegrityUtils.getBytesFromHexDigest(value); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java deleted file mode 100644 index 2cbd4ede5214..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Holds the indexing type and indexing key of a given formula. */ -class RuleIndexingDetails { - - static final int NOT_INDEXED = 0; - static final int PACKAGE_NAME_INDEXED = 1; - static final int APP_CERTIFICATE_INDEXED = 2; - - static final String DEFAULT_RULE_KEY = "N/A"; - - /** Represents which indexed file the rule should be located. */ - @IntDef( - value = { - NOT_INDEXED, - PACKAGE_NAME_INDEXED, - APP_CERTIFICATE_INDEXED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndexType { - } - - private @IndexType int mIndexType; - private String mRuleKey; - - /** Constructor without a ruleKey for {@code NOT_INDEXED}. */ - RuleIndexingDetails(@IndexType int indexType) { - this.mIndexType = indexType; - this.mRuleKey = DEFAULT_RULE_KEY; - } - - /** Constructor with a ruleKey for indexed rules. */ - RuleIndexingDetails(@IndexType int indexType, String ruleKey) { - this.mIndexType = indexType; - this.mRuleKey = ruleKey; - } - - /** Returns the indexing type for the rule. */ - @IndexType - public int getIndexType() { - return mIndexType; - } - - /** Returns the identified rule key. */ - public String getRuleKey() { - return mRuleKey; - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java deleted file mode 100644 index e7235591fb9b..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** A helper class for identifying the indexing type and key of a given rule. */ -class RuleIndexingDetailsIdentifier { - - /** - * Splits a given rule list into three indexing categories. Each rule category is returned as a - * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for - * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for - * NOT_INDEXED rules. - */ - public static Map>> splitRulesIntoIndexBuckets( - List rules) { - if (rules == null) { - throw new IllegalArgumentException( - "Index buckets cannot be created for null rule list."); - } - - Map>> typeOrganizedRuleMap = new HashMap(); - typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap()); - typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>()); - typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>()); - - // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the - // entries sorted by their index key. - for (Rule rule : rules) { - RuleIndexingDetails indexingDetails; - try { - indexingDetails = getIndexingDetails(rule.getFormula()); - } catch (Exception e) { - throw new IllegalArgumentException( - String.format("Malformed rule identified. [%s]", rule.toString())); - } - - int ruleIndexType = indexingDetails.getIndexType(); - String ruleKey = indexingDetails.getRuleKey(); - - if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) { - typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList()); - } - - typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule); - } - - return typeOrganizedRuleMap; - } - - private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) { - switch (formula.getTag()) { - case IntegrityFormula.COMPOUND_FORMULA_TAG: - return getIndexingDetailsForCompoundFormula((CompoundFormula) formula); - case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG: - return getIndexingDetailsForStringAtomicFormula( - (AtomicFormula.StringAtomicFormula) formula); - case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG: - case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG: - case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG: - // Package name and app certificate related formulas are string atomic formulas. - return new RuleIndexingDetails(NOT_INDEXED); - default: - throw new IllegalArgumentException( - String.format("Invalid formula tag type: %s", formula.getTag())); - } - } - - private static RuleIndexingDetails getIndexingDetailsForCompoundFormula( - CompoundFormula compoundFormula) { - int connector = compoundFormula.getConnector(); - List formulas = compoundFormula.getFormulas(); - - switch (connector) { - case CompoundFormula.AND: - case CompoundFormula.OR: - // If there is a package name related atomic rule, return package name indexed. - Optional packageNameRule = - formulas.stream() - .map(formula -> getIndexingDetails(formula)) - .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType() - == PACKAGE_NAME_INDEXED) - .findAny(); - if (packageNameRule.isPresent()) { - return packageNameRule.get(); - } - - // If there is an app certificate related atomic rule but no package name related - // atomic rule, return app certificate indexed. - Optional appCertificateRule = - formulas.stream() - .map(formula -> getIndexingDetails(formula)) - .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType() - == APP_CERTIFICATE_INDEXED) - .findAny(); - if (appCertificateRule.isPresent()) { - return appCertificateRule.get(); - } - - // Do not index when there is not package name or app certificate indexing. - return new RuleIndexingDetails(NOT_INDEXED); - default: - // Having a NOT operator in the indexing messes up the indexing; e.g., deny - // installation if app certificate is NOT X (should not be indexed with app cert - // X). We will not keep these rules indexed. - // Also any other type of unknown operators will not be indexed. - return new RuleIndexingDetails(NOT_INDEXED); - } - } - - private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula( - AtomicFormula.StringAtomicFormula atomicFormula) { - switch (atomicFormula.getKey()) { - case AtomicFormula.PACKAGE_NAME: - return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue()); - case AtomicFormula.APP_CERTIFICATE: - return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue()); - default: - return new RuleIndexingDetails(NOT_INDEXED); - } - } -} - diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java deleted file mode 100644 index 022b4b8cb7eb..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG; -import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG; - -import android.util.Xml; - -import com.android.modules.utils.TypedXmlSerializer; -import com.android.server.integrity.model.RuleMetadata; - -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; - -/** Helper class for writing rule metadata. */ -public class RuleMetadataSerializer { - /** Serialize the rule metadata to an output stream. */ - public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream) - throws IOException { - TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream); - - serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider()); - serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion()); - - xmlSerializer.endDocument(); - } - - private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag, - String value) throws IOException { - xmlSerializer.startTag(/* namespace= */ null, tag); - xmlSerializer.text(value); - xmlSerializer.endTag(/* namespace= */ null, tag); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java deleted file mode 100644 index 60cfc4876414..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import android.annotation.NonNull; - -/** - * Thrown when rule serialization fails. - */ -public class RuleSerializeException extends Exception { - public RuleSerializeException(@NonNull String message) { - super(message); - } - - public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) { - super(message, cause); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java deleted file mode 100644 index 2941856915a8..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import android.content.integrity.Rule; - -import java.io.OutputStream; -import java.util.List; -import java.util.Optional; - -/** A helper class to serialize rules from the {@link Rule} model. */ -public interface RuleSerializer { - - /** Serialize rules to an output stream */ - void serialize( - List rules, - Optional formatVersion, - OutputStream ruleFileOutputStream, - OutputStream indexingFileOutputStream) - throws RuleSerializeException; - - /** Serialize rules to a ByteArray. */ - byte[] serialize(List rules, Optional formatVersion) - throws RuleSerializeException; -} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java deleted file mode 100644 index 9ed2e88bd6a2..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ /dev/null @@ -1,914 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; -import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; -import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; -import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; -import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; -import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; -import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; -import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT; -import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT; -import static com.android.server.integrity.utils.TestUtils.getBits; -import static com.android.server.integrity.utils.TestUtils.getBytes; -import static com.android.server.integrity.utils.TestUtils.getValueBits; -import static com.android.server.testutils.TestUtils.assertExpectException; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.IntegrityUtils; -import android.content.integrity.Rule; - -import androidx.annotation.NonNull; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -@RunWith(JUnit4.class) -public class RuleBinarySerializerTest { - - private static final String SAMPLE_INSTALLER_NAME = "com.test.installer"; - private static final String SAMPLE_INSTALLER_CERT = "installer_cert"; - - private static final String COMPOUND_FORMULA_START_BITS = - getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); - private static final String COMPOUND_FORMULA_END_BITS = - getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); - private static final String ATOMIC_FORMULA_START_BITS = - getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); - - private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); - private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS); - private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS); - - private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); - private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS); - private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS); - private static final String INSTALLER_CERTIFICATE = - getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS); - private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS); - private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS); - - private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); - - private static final String IS_NOT_HASHED = "0"; - private static final String IS_HASHED = "1"; - - private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); - - private static final String START_BIT = "1"; - private static final String END_BIT = "1"; - - private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = - getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); - - private static final String SERIALIZED_START_INDEXING_KEY = - IS_NOT_HASHED - + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS) - + getValueBits(START_INDEXING_KEY); - private static final String SERIALIZED_END_INDEXING_KEY = - IS_NOT_HASHED - + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS) - + getValueBits(END_INDEXING_KEY); - - @Test - public void testBinaryString_serializeNullRules() { - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.", - () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty())); - } - - @Test - public void testBinaryString_emptyRules() throws Exception { - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - binarySerializer.serialize( - Collections.emptyList(), - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream); - - ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream(); - expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - assertThat(ruleOutputStream.toByteArray()) - .isEqualTo(expectedRuleOutputStream.toByteArray()); - - ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); - String serializedIndexingBytes = - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); - byte[] expectedIndexingBytes = - getBytes( - serializedIndexingBytes - + serializedIndexingBytes - + serializedIndexingBytes); - expectedIndexingOutputStream.write(expectedIndexingBytes); - assertThat(indexingOutputStream.toByteArray()) - .isEqualTo(expectedIndexingOutputStream.toByteArray()); - } - - @Test - public void testBinaryStream_serializeValidCompoundFormula() throws Exception { - String packageName = "com.test.app"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false))), - Rule.DENY); - - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - binarySerializer.serialize( - Collections.singletonList(rule), - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream); - - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream(); - expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - expectedRuleOutputStream.write(getBytes(expectedBits)); - assertThat(ruleOutputStream.toByteArray()) - .isEqualTo(expectedRuleOutputStream.toByteArray()); - - ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); - String expectedIndexingBitsForIndexed = - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); - String expectedIndexingBitsForUnindexed = - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits( - DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length, - /* numOfBits= */ 32); - expectedIndexingOutputStream.write( - getBytes( - expectedIndexingBitsForIndexed - + expectedIndexingBitsForIndexed - + expectedIndexingBitsForUnindexed)); - - assertThat(indexingOutputStream.toByteArray()) - .isEqualTo(expectedIndexingOutputStream.toByteArray()); - } - - @Test - public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception { - String packageName = "com.test.app"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception { - String packageName = "com.test.app"; - String appCertificate = "test_cert"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - appCertificate, - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception { - String packageName = "com.test.app"; - String appCertificate = "test_cert"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.OR, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - appCertificate, - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + OR - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception { - String packageName = "com.test.app"; - Rule rule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception { - String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - Rule rule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - IntegrityUtils.getHexDigest( - appCertificate.getBytes(StandardCharsets.UTF_8)), - /* isHashedValue= */ true), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception { - long versionCode = 1; - Rule rule = - new Rule( - new AtomicFormula.LongAtomicFormula( - AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + VERSION_CODE - + EQ - + getBits(versionCode, /* numOfBits= */ 64) - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception { - String preInstalled = "1"; - Rule rule = - new Rule( - new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + PRE_INSTALLED - + EQ - + preInstalled - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeInvalidFormulaType() throws Exception { - IntegrityFormula invalidFormula = getInvalidFormula(); - Rule rule = new Rule(invalidFormula, Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - /* expectedExceptionMessageRegex= */ "Malformed rule identified.", - () -> - binarySerializer.serialize( - Collections.singletonList(rule), - /* formatVersion= */ Optional.empty())); - } - - @Test - public void testBinaryString_serializeFormatVersion() throws Exception { - int formatVersion = 1; - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS); - byte[] expectedRules = getBytes(expectedBits); - - byte[] actualRules = - binarySerializer.serialize( - Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion)); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception { - int ruleCount = 225; - String packagePrefix = "package.name."; - String appCertificatePrefix = "app.cert."; - String installerNamePrefix = "installer."; - - // Create the rule set with 225 package name based rules, 225 app certificate indexed rules, - // and 225 non-indexed rules.. - List ruleList = new ArrayList(); - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithPackageNameAndSampleInstallerName( - String.format("%s%04d", packagePrefix, count))); - } - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithAppCertificateAndSampleInstallerName( - String.format("%s%04d", appCertificatePrefix, count))); - } - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getNonIndexedRuleWithInstallerName( - String.format("%s%04d", installerNamePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream); - - // Verify the rules file and index files. - ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); - - expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length; - - String expectedIndexingBytesForPackageNameIndexed = - SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - for (int count = 0; count < ruleCount; count++) { - String packageName = String.format("%s%04d", packagePrefix, count); - if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) { - expectedIndexingBytesForPackageNameIndexed += - IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + getBits(totalBytesWritten, /* numOfBits= */ 32); - } - - byte[] bytesForPackage = - getBytes( - getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( - packageName)); - expectedOrderedRuleOutputStream.write(bytesForPackage); - totalBytesWritten += bytesForPackage.length; - } - expectedIndexingBytesForPackageNameIndexed += - SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - - String expectedIndexingBytesForAppCertificateIndexed = - SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - for (int count = 0; count < ruleCount; count++) { - String appCertificate = String.format("%s%04d", appCertificatePrefix, count); - if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) { - expectedIndexingBytesForAppCertificateIndexed += - IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + getBits(totalBytesWritten, /* numOfBits= */ 32); - } - - byte[] bytesForPackage = - getBytes( - getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( - appCertificate)); - expectedOrderedRuleOutputStream.write(bytesForPackage); - totalBytesWritten += bytesForPackage.length; - } - expectedIndexingBytesForAppCertificateIndexed += - SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - - String expectedIndexingBytesForUnindexed = - SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - for (int count = 0; count < ruleCount; count++) { - byte[] bytesForPackage = - getBytes( - getSerializedCompoundRuleWithInstallerNameAndInstallerCert( - String.format("%s%04d", installerNamePrefix, count))); - expectedOrderedRuleOutputStream.write(bytesForPackage); - totalBytesWritten += bytesForPackage.length; - } - expectedIndexingBytesForUnindexed += - SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write( - getBytes( - expectedIndexingBytesForPackageNameIndexed - + expectedIndexingBytesForAppCertificateIndexed - + expectedIndexingBytesForUnindexed)); - - assertThat(ruleOutputStream.toByteArray()) - .isEqualTo(expectedOrderedRuleOutputStream.toByteArray()); - assertThat(indexingOutputStream.toByteArray()) - .isEqualTo(expectedIndexingOutputStream.toByteArray()); - } - - @Test - public void testBinaryString_totalRuleSizeLimitReached() { - int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1; - String packagePrefix = "package.name."; - String appCertificatePrefix = "app.cert."; - String installerNamePrefix = "installer."; - - // Create the rule set with more rules than the system can handle in total. - List ruleList = new ArrayList(); - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithPackageNameAndSampleInstallerName( - String.format("%s%04d", packagePrefix, count))); - } - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithAppCertificateAndSampleInstallerName( - String.format("%s%04d", appCertificatePrefix, count))); - } - for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) { - ruleList.add( - getNonIndexedRuleWithInstallerName( - String.format("%s%04d", installerNamePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - @Test - public void testBinaryString_tooManyPackageNameIndexedRules() { - String packagePrefix = "package.name."; - - // Create a rule set with too many package name indexed rules. - List ruleList = new ArrayList(); - for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) { - ruleList.add( - getRuleWithPackageNameAndSampleInstallerName( - String.format("%s%04d", packagePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided in the indexing group.", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - @Test - public void testBinaryString_tooManyAppCertificateIndexedRules() { - String appCertificatePrefix = "app.cert."; - - // Create a rule set with too many app certificate indexed rules. - List ruleList = new ArrayList(); - for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) { - ruleList.add( - getRuleWithAppCertificateAndSampleInstallerName( - String.format("%s%04d", appCertificatePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided in the indexing group.", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - @Test - public void testBinaryString_tooManyNonIndexedRules() { - String installerNamePrefix = "installer."; - - // Create a rule set with too many unindexed rules. - List ruleList = new ArrayList(); - for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) { - ruleList.add( - getNonIndexedRuleWithInstallerName( - String.format("%s%04d", installerNamePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided in the indexing group.", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( - String packageName) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + INSTALLER_NAME - + EQ - + IS_NOT_HASHED - + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS) - + getValueBits(SAMPLE_INSTALLER_NAME) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - } - - private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - certificate, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( - String appCertificate) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + ATOMIC_FORMULA_START_BITS - + INSTALLER_NAME - + EQ - + IS_NOT_HASHED - + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS) - + getValueBits(SAMPLE_INSTALLER_NAME) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - } - - private Rule getNonIndexedRuleWithInstallerName(String installerName) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - installerName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_CERTIFICATE, - SAMPLE_INSTALLER_CERT, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert( - String installerName) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + INSTALLER_NAME - + EQ - + IS_NOT_HASHED - + getBits(installerName.length(), VALUE_SIZE_BITS) - + getValueBits(installerName) - + ATOMIC_FORMULA_START_BITS - + INSTALLER_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS) - + getValueBits(SAMPLE_INSTALLER_CERT) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - } - - private static IntegrityFormula getInvalidFormula() { - return new AtomicFormula(0) { - @Override - public int getTag() { - return 0; - } - - @Override - public boolean matches(AppInstallMetadata appInstallMetadata) { - return false; - } - - @Override - public boolean isAppCertificateFormula() { - return false; - } - - @Override - public boolean isAppCertificateLineageFormula() { - return false; - } - - @Override - public boolean isInstallerFormula() { - return false; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @NonNull - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public String toString() { - return super.toString(); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - } - }; - } -} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java deleted file mode 100644 index 6dccdf51af02..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets; -import static com.android.server.testutils.TestUtils.assertExpectException; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; - -import androidx.annotation.NonNull; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */ -@RunWith(JUnit4.class) -public class RuleIndexingDetailsIdentifierTest { - - private static final String SAMPLE_APP_CERTIFICATE = "testcert"; - private static final String SAMPLE_INSTALLER_NAME = "com.test.installer"; - private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert"; - private static final String SAMPLE_PACKAGE_NAME = "com.test.package"; - - private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - SAMPLE_PACKAGE_NAME, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - SAMPLE_APP_CERTIFICATE, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_CERTIFICATE, - SAMPLE_INSTALLER_CERTIFICATE, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE = - new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, - AtomicFormula.EQ, 12); - private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED = - new AtomicFormula.BooleanAtomicFormula( - AtomicFormula.PRE_INSTALLED, /* booleanValue= */ - true); - - - private static final Rule RULE_WITH_PACKAGE_NAME = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_PACKAGE_NAME, - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - private static final Rule RULE_WITH_APP_CERTIFICATE = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_APP_CERTIFICATE, - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_INSTALLER_NAME, - ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)), - Rule.DENY); - - private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_VERSION_CODE, - ATOMIC_FORMULA_WITH_ISPREINSTALLED)), - Rule.DENY); - public static final int INVALID_FORMULA_TAG = -1; - - @Test - public void getIndexType_nullRule() { - List ruleList = null; - - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex= */ - "Index buckets cannot be created for null rule list.", - () -> splitRulesIntoIndexBuckets(ruleList)); - } - - @Test - public void getIndexType_invalidFormula() { - List ruleList = new ArrayList(); - ruleList.add(new Rule(getInvalidFormula(), Rule.DENY)); - - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex= */ "Malformed rule identified.", - () -> splitRulesIntoIndexBuckets(ruleList)); - } - - @Test - public void getIndexType_ruleContainingPackageNameFormula() { - List ruleList = new ArrayList(); - ruleList.add(RULE_WITH_PACKAGE_NAME); - - Map>> result = splitRulesIntoIndexBuckets(ruleList); - - // Verify the resulting map content. - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(NOT_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME); - assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME)) - .containsExactly(RULE_WITH_PACKAGE_NAME); - } - - @Test - public void getIndexType_ruleContainingAppCertificateFormula() { - List ruleList = new ArrayList(); - ruleList.add(RULE_WITH_APP_CERTIFICATE); - - Map>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(NOT_INDEXED)).isEmpty(); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()) - .containsExactly(SAMPLE_APP_CERTIFICATE); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE)) - .containsExactly(RULE_WITH_APP_CERTIFICATE); - } - - @Test - public void getIndexType_ruleWithUnindexedCompoundFormula() { - List ruleList = new ArrayList(); - ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); - - Map>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS); - } - - @Test - public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() { - List ruleList = new ArrayList(); - ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - - Map>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS); - } - - @Test - public void getIndexType_negatedRuleContainingPackageNameFormula() { - Rule negatedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Arrays.asList( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_PACKAGE_NAME, - ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))), - Rule.DENY); - List ruleList = new ArrayList(); - ruleList.add(negatedRule); - - Map>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule); - } - - @Test - public void getIndexType_allRulesTogetherSplitCorrectly() { - Rule packageNameRuleA = getRuleWithPackageName("aaa"); - Rule packageNameRuleB = getRuleWithPackageName("bbb"); - Rule packageNameRuleC = getRuleWithPackageName("ccc"); - Rule certificateRule1 = getRuleWithAppCertificate("cert1"); - Rule certificateRule2 = getRuleWithAppCertificate("cert2"); - Rule certificateRule3 = getRuleWithAppCertificate("cert3"); - - List ruleList = new ArrayList(); - ruleList.add(packageNameRuleB); - ruleList.add(packageNameRuleC); - ruleList.add(packageNameRuleA); - ruleList.add(certificateRule3); - ruleList.add(certificateRule2); - ruleList.add(certificateRule1); - ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); - ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - - Map>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - - // We check asserts this way to ensure ordering based on package name. - assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc"); - - // We check asserts this way to ensure ordering based on app certificate. - assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2", - "cert3"); - - assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS, - RULE_WITH_NONSTRING_RESTRICTIONS); - } - - private Rule getRuleWithPackageName(String packageName) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - } - - private Rule getRuleWithAppCertificate(String certificate) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - certificate, - /* isHashedValue= */ false), - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - } - - private IntegrityFormula getInvalidFormula() { - return new AtomicFormula(0) { - @Override - public int getTag() { - return INVALID_FORMULA_TAG; - } - - @Override - public boolean matches(AppInstallMetadata appInstallMetadata) { - return false; - } - - @Override - public boolean isAppCertificateFormula() { - return false; - } - - @Override - public boolean isAppCertificateLineageFormula() { - return false; - } - - @Override - public boolean isInstallerFormula() { - return false; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @NonNull - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public String toString() { - return super.toString(); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - } - }; - } -} -- GitLab From 965caedb30bbb9bd954dbc1efe857c114fc6e359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Go=CC=88llner?= Date: Tue, 29 Oct 2024 09:48:38 +0000 Subject: [PATCH 014/509] Show privacy dot on connected displays Introduces PrivacyDotWindowController, which adds the privacy dots to WindowManager. This controller is only used for connected displays for now, and the old logic in ScreenDecorations is used for the default/main display. Also extracts the ScreenDecorations ui thread to a Dagger module, so it can be reused in PrivacyDotWindowController. Test: PrivacyDotWindowControllerTest Test: PrivacyDotWindowControllerStoreImplTest Test: PrivacyDotViewControllerTest Test: DisplayWindowPropertiesRepositoryImplTest Test: Add a connected display and start a voice recording. Also change rotation of the display and see that the dot positions correctly. Bug: 362720432 Flag: com.android.systemui.status_bar_connected_displays Change-Id: I5f0b8ddb37b55a53b66f3357bc1279e4b765e744 --- .../core/MultiDisplayStatusBarStarterTest.kt | 63 +++++- ...PrivacyDotWindowControllerStoreImplTest.kt | 54 +++++ .../events/PrivacyDotWindowControllerTest.kt | 185 ++++++++++++++++++ .../android/systemui/ScreenDecorations.java | 15 +- .../decor/PrivacyDotDecorProviderFactory.kt | 2 +- .../core/MultiDisplayStatusBarStarter.kt | 13 +- .../PrivacyDotWindowControllerStore.kt | 99 ++++++++++ .../events/PrivacyDotWindowController.kt | 112 +++++++++++ .../phone/dagger/StatusBarPhoneModule.kt | 8 +- .../src/android/view/FakeWindowManager.kt | 61 ++++++ .../src/android/view/WindowManagerKosmos.kt | 3 + .../ViewCaptureAwareWindowManagerKosmos.kt | 10 + .../systemui/SysuiTestableContext.java | 17 ++ .../PrivacyDotDecorProviderFactoryKosmos.kt | 25 +++ .../core/StatusBarOrchestratorKosmos.kt | 2 + .../FakePrivacyDotViewControllerStore.kt | 32 +++ .../FakePrivacyDotWindowControllerStore.kt | 33 ++++ .../PrivacyDotViewControllerStoreKosmos.kt | 25 +++ .../PrivacyDotWindowControllerStoreKosmos.kt | 50 +++++ .../events/FakePrivacyDotViewController.kt | 63 ++++++ .../events/PrivacyDotViewControllerKosmos.kt | 26 +++ .../PrivacyDotWindowControllerKosmos.kt | 36 ++++ 22 files changed, 920 insertions(+), 14 deletions(-) create mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt create mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt create mode 100644 packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt index 8dcc44463213..5be5fb4aa9ba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.core import android.platform.test.annotations.EnableFlags +import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowControllerStore import com.android.systemui.testKosmos import com.google.common.truth.Expect import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -30,6 +32,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.never import org.mockito.kotlin.verify @OptIn(ExperimentalCoroutinesApi::class) @@ -39,16 +42,12 @@ import org.mockito.kotlin.verify class MultiDisplayStatusBarStarterTest : SysuiTestCase() { @get:Rule val expect: Expect = Expect.create() - private val kosmos = - testKosmos().also { - it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory - it.statusBarInitializerStore = it.fakeStatusBarInitializerStore - } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val fakeDisplayRepository = kosmos.displayRepository private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore - + private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore // Lazy, so that @EnableFlags is set before initializer is instantiated. private val underTest by lazy { kosmos.multiDisplayStatusBarStarter } @@ -82,6 +81,31 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() { verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start() } + @Test + fun start_startsPrivacyDotForCurrentDisplays() = + testScope.runTest { + fakeDisplayRepository.addDisplay(displayId = 1) + fakeDisplayRepository.addDisplay(displayId = 2) + + underTest.start() + runCurrent() + + verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start() + verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start() + } + + @Test + fun start_doesNotStartPrivacyDotForDefaultDisplay() = + testScope.runTest { + fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY) + + underTest.start() + runCurrent() + + verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never()) + .start() + } + @Test fun displayAdded_orchestratorForNewDisplayIsStarted() = testScope.runTest { @@ -108,6 +132,18 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() { .isTrue() } + @Test + fun displayAdded_privacyDotForNewDisplayIsStarted() = + testScope.runTest { + underTest.start() + runCurrent() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start() + } + @Test fun displayAddedDuringStart_initializerForNewDisplayIsStarted() = testScope.runTest { @@ -129,8 +165,17 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() { fakeDisplayRepository.addDisplay(displayId = 3) runCurrent() - expect - .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable) - .isTrue() + verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start() + } + + @Test + fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() = + testScope.runTest { + underTest.start() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt new file mode 100644 index 000000000000..ae734b3ca04f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt @@ -0,0 +1,54 @@ +/* + * 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.statusbar.data.repository + +import android.platform.test.annotations.EnableFlags +import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.testKosmos +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl } + + @Before + fun installDisplays() = runBlocking { + kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY) + kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY + 1) + } + + @Test(expected = IllegalArgumentException::class) + fun forDisplay_defaultDisplay_throws() { + underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY) + } + + @Test + fun forDisplay_nonDefaultDisplay_doesNotThrow() { + underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY + 1) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt new file mode 100644 index 000000000000..6bcd735e9a9f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt @@ -0,0 +1,185 @@ +/* + * 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.statusbar.events + +import android.view.Gravity.BOTTOM +import android.view.Gravity.LEFT +import android.view.Gravity.RIGHT +import android.view.Gravity.TOP +import android.view.Surface +import android.view.View +import android.view.WindowManager +import android.view.fakeWindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.google.common.truth.Expect +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrivacyDotWindowControllerTest : SysuiTestCase() { + + @get:Rule val expect: Expect = Expect.create() + + private val kosmos = testKosmos() + private val underTest = kosmos.privacyDotWindowController + private val viewController = kosmos.privacyDotViewController + private val windowManager = kosmos.fakeWindowManager + private val executor = kosmos.fakeExecutor + + @After + fun cleanUpCustomDisplay() { + context.display = null + } + + @Test + fun start_beforeUiThreadExecutes_doesNotAddWindows() { + underTest.start() + + assertThat(windowManager.addedViews).isEmpty() + } + + @Test + fun start_beforeUiThreadExecutes_doesNotInitializeViewController() { + underTest.start() + + assertThat(viewController.isInitialized).isFalse() + } + + @Test + fun start_afterUiThreadExecutes_addsWindowsOnUiThread() { + underTest.start() + + executor.runAllReady() + + assertThat(windowManager.addedViews).hasSize(4) + } + + @Test + fun start_afterUiThreadExecutes_initializesViewController() { + underTest.start() + + executor.runAllReady() + + assertThat(viewController.isInitialized).isTrue() + } + + @Test + fun start_initializesTopLeft() { + underTest.start() + executor.runAllReady() + + assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container) + } + + @Test + fun start_initializesTopRight() { + underTest.start() + executor.runAllReady() + + assertThat(viewController.topRight?.id).isEqualTo(R.id.privacy_dot_top_right_container) + } + + @Test + fun start_initializesTopBottomLeft() { + underTest.start() + executor.runAllReady() + + assertThat(viewController.bottomLeft?.id).isEqualTo(R.id.privacy_dot_bottom_left_container) + } + + @Test + fun start_initializesBottomRight() { + underTest.start() + executor.runAllReady() + + assertThat(viewController.bottomRight?.id) + .isEqualTo(R.id.privacy_dot_bottom_right_container) + } + + @Test + fun start_viewsAddedInRespectiveCorners() { + context.display = mock { on { rotation } doReturn Surface.ROTATION_0 } + + underTest.start() + executor.runAllReady() + + expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT) + expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT) + expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT) + expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or RIGHT) + } + + @Test + fun start_rotation90_viewsPositionIsShifted90degrees() { + context.display = mock { on { rotation } doReturn Surface.ROTATION_90 } + + underTest.start() + executor.runAllReady() + + expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT) + expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT) + expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT) + expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or RIGHT) + } + + @Test + fun start_rotation180_viewsPositionIsShifted180degrees() { + context.display = mock { on { rotation } doReturn Surface.ROTATION_180 } + + underTest.start() + executor.runAllReady() + + expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT) + expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT) + expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT) + expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or LEFT) + } + + @Test + fun start_rotation270_viewsPositionIsShifted270degrees() { + context.display = mock { on { rotation } doReturn Surface.ROTATION_270 } + + underTest.start() + executor.runAllReady() + + expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT) + expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT) + expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT) + expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or LEFT) + } + + private fun paramsForView(view: View): WindowManager.LayoutParams { + return windowManager.addedViews.entries + .first { it.key == view || it.key.findViewById(view.id) != null } + .value + } + + private fun gravityForView(view: View): Int { + return paramsForView(view).gravity + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 46e45aaf8a8a..66b3e189b6c9 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -905,7 +905,18 @@ public class ScreenDecorations implements return lp; } - private WindowManager.LayoutParams getWindowLayoutBaseParams() { + public static WindowManager.LayoutParams getWindowLayoutBaseParams() { + return getWindowLayoutBaseParams(/* excludeFromScreenshots= */ true); + } + + /** + * Creates the base {@link WindowManager.LayoutParams} that are used for all decoration windows. + * + * @param excludeFromScreenshots whether to set the {@link + * WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY} flag. + */ + public static WindowManager.LayoutParams getWindowLayoutBaseParams( + boolean excludeFromScreenshots) { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE @@ -921,7 +932,7 @@ public class ScreenDecorations implements // FLAG_SLIPPERY can only be set by trusted overlays lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; - if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) { + if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS && excludeFromScreenshots) { lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; } diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt index 9aa7fd100068..78d8d8fe4e48 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt @@ -81,7 +81,7 @@ class PrivacyDotCornerDecorProviderImpl( override val viewId: Int, @DisplayCutout.BoundsPosition override val alignedBound1: Int, @DisplayCutout.BoundsPosition override val alignedBound2: Int, - private val layoutId: Int, + val layoutId: Int, ) : CornerDecorProvider() { override fun onReloadResAndMeasure( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt index e1159220e366..9b3513e8a363 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.core import android.view.Display +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.DisplayScopeRepository +import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore @@ -29,7 +31,6 @@ import com.android.systemui.util.kotlin.pairwiseBy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.onStart -import com.android.app.tracing.coroutines.launchTraced as launch /** * Responsible for creating and starting the status bar components for each display. Also does it @@ -48,6 +49,7 @@ constructor( private val initializerStore: StatusBarInitializerStore, private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val statusBarInitializerStore: StatusBarInitializerStore, + private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore, ) : CoreStartable { init { @@ -71,6 +73,7 @@ constructor( val displayId = display.displayId createAndStartOrchestratorForDisplay(displayId) createAndStartInitializerForDisplay(displayId) + startPrivacyDotForDisplay(displayId) } private fun createAndStartOrchestratorForDisplay(displayId: Int) { @@ -89,4 +92,12 @@ constructor( private fun createAndStartInitializerForDisplay(displayId: Int) { statusBarInitializerStore.forDisplay(displayId).start() } + + private fun startPrivacyDotForDisplay(displayId: Int) { + if (displayId == Display.DEFAULT_DISPLAY) { + // For the default display, privacy dot is started via ScreenDecorations + return + } + privacyDotWindowControllerStore.forDisplay(displayId).start() + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt new file mode 100644 index 000000000000..a1f56552629b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt @@ -0,0 +1,99 @@ +/* + * 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.statusbar.data.repository + +import android.view.Display +import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL +import com.android.app.viewcapture.ViewCaptureAwareWindowManager +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.events.PrivacyDotWindowController +import dagger.Binds +import dagger.Lazy +import dagger.Module +import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope + +/** Providers per display instances of [PrivacyDotWindowController]. */ +interface PrivacyDotWindowControllerStore : PerDisplayStore + +@SysUISingleton +class PrivacyDotWindowControllerStoreImpl +@Inject +constructor( + @Background backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, + private val windowControllerFactory: PrivacyDotWindowController.Factory, + private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, + private val privacyDotViewControllerStore: PrivacyDotViewControllerStore, + private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory, +) : + PrivacyDotWindowControllerStore, + PerDisplayStoreImpl(backgroundApplicationScope, displayRepository) { + + init { + StatusBarConnectedDisplays.assertInNewMode() + } + + override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController { + if (displayId == Display.DEFAULT_DISPLAY) { + throw IllegalArgumentException("This class should only be used for connected displays") + } + val displayWindowProperties = + displayWindowPropertiesRepository.get(displayId, TYPE_NAVIGATION_BAR_PANEL) + return windowControllerFactory.create( + displayId = displayId, + privacyDotViewController = privacyDotViewControllerStore.forDisplay(displayId), + viewCaptureAwareWindowManager = + viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager), + inflater = displayWindowProperties.layoutInflater, + ) + } + + override val instanceClass = PrivacyDotWindowController::class.java +} + +@Module +interface PrivacyDotWindowControllerStoreModule { + + @Binds fun store(impl: PrivacyDotWindowControllerStoreImpl): PrivacyDotWindowControllerStore + + companion object { + @Provides + @SysUISingleton + @IntoMap + @ClassKey(PrivacyDotWindowControllerStore::class) + fun storeAsCoreStartable( + storeLazy: Lazy + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + storeLazy.get() + } else { + CoreStartable.NOP + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt new file mode 100644 index 000000000000..9928ac67f185 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt @@ -0,0 +1,112 @@ +/* + * 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.statusbar.events + +import android.view.Display +import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM +import android.view.DisplayCutout.BOUNDS_POSITION_LEFT +import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT +import android.view.DisplayCutout.BOUNDS_POSITION_TOP +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout +import com.android.app.viewcapture.ViewCaptureAwareWindowManager +import com.android.systemui.ScreenDecorations +import com.android.systemui.ScreenDecorationsThread +import com.android.systemui.decor.DecorProvider +import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl +import com.android.systemui.decor.PrivacyDotDecorProviderFactory +import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft +import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight +import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft +import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight +import com.android.systemui.util.containsExactly +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.concurrent.Executor + +/** + * Responsible for adding the privacy dot to a window. + * + * It will create one window per corner (top left, top right, bottom left, bottom right), which are + * used dependant on the display's rotation. + */ +class PrivacyDotWindowController +@AssistedInject +constructor( + @Assisted private val displayId: Int, + @Assisted private val privacyDotViewController: PrivacyDotViewController, + @Assisted private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, + @Assisted private val inflater: LayoutInflater, + @ScreenDecorationsThread private val uiExecutor: Executor, + private val dotFactory: PrivacyDotDecorProviderFactory, +) { + + fun start() { + uiExecutor.execute { startOnUiThread() } + } + + private fun startOnUiThread() { + val providers = dotFactory.providers + + val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT) + val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT) + val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT) + val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT) + + topLeft.addToWindow(TopLeft) + topRight.addToWindow(TopRight) + bottomLeft.addToWindow(BottomLeft) + bottomRight.addToWindow(BottomRight) + + privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight) + } + + private fun List.inflate(alignedBound1: Int, alignedBound2: Int): View { + val provider = + first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) } + as PrivacyDotCornerDecorProviderImpl + return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null) + } + + private fun View.addToWindow(corner: PrivacyDotCorner) { + val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY + val params = + ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply { + width = WRAP_CONTENT + height = WRAP_CONTENT + gravity = corner.rotatedCorner(context.display.rotation).gravity + title = "PrivacyDot${corner.title}$displayId" + } + // PrivacyDotViewController expects the dot view to have a FrameLayout parent. + val rootView = FrameLayout(context) + rootView.addView(this) + viewCaptureAwareWindowManager.addView(rootView, params) + } + + @AssistedFactory + fun interface Factory { + fun create( + displayId: Int, + privacyDotViewController: PrivacyDotViewController, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, + inflater: LayoutInflater, + ): PrivacyDotWindowController + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt index e4a75beca9f9..cc99e071ecd3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.core.StatusBarInitializerStore import com.android.systemui.statusbar.core.StatusBarOrchestrator import com.android.systemui.statusbar.core.StatusBarSimpleFragment import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule +import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks @@ -48,7 +49,12 @@ import kotlinx.coroutines.CoroutineScope /** Similar in purpose to [StatusBarModule], but scoped only to phones */ @Module( - includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class] + includes = + [ + PrivacyDotViewControllerModule::class, + PrivacyDotWindowControllerStoreModule::class, + PrivacyDotViewControllerStoreModule::class, + ] ) interface StatusBarPhoneModule { diff --git a/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt new file mode 100644 index 000000000000..c28449feeab7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt @@ -0,0 +1,61 @@ +/* + * 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.view + +import android.content.Context +import android.graphics.Region +import android.view.WindowManager.LayoutParams + +class FakeWindowManager(private val context: Context) : WindowManager { + + val addedViews = mutableMapOf() + + override fun addView(view: View, params: ViewGroup.LayoutParams) { + addedViews[view] = params as LayoutParams + } + + override fun removeView(view: View) { + addedViews.remove(view) + } + + override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) { + addedViews[view] = params as LayoutParams + } + + override fun getApplicationLaunchKeyboardShortcuts(deviceId: Int): KeyboardShortcutGroup { + return KeyboardShortcutGroup("Fake group") + } + + override fun getCurrentImeTouchRegion(): Region { + return Region.obtain() + } + + override fun getDefaultDisplay(): Display { + return context.display + } + + override fun removeViewImmediate(view: View) { + addedViews.remove(view) + } + + override fun requestAppKeyboardShortcuts( + receiver: WindowManager.KeyboardShortcutsReceiver, + deviceId: Int, + ) { + receiver.onKeyboardShortcutsReceived(emptyList()) + } +} diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt index d5451ee8eb10..025f556991f2 100644 --- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt @@ -16,9 +16,12 @@ package android.view +import android.content.applicationContext import com.android.systemui.kosmos.Kosmos import org.mockito.Mockito.mock +val Kosmos.fakeWindowManager by Kosmos.Fixture { FakeWindowManager(applicationContext) } + val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) } var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager } diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt index e1c6699348a9..021c7bbb44cd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt @@ -16,11 +16,21 @@ package com.android.app.viewcapture +import android.view.fakeWindowManager import com.android.systemui.kosmos.Kosmos import org.mockito.kotlin.mock val Kosmos.mockViewCaptureAwareWindowManager by Kosmos.Fixture { mock() } +val Kosmos.realCaptureAwareWindowManager by + Kosmos.Fixture { + ViewCaptureAwareWindowManager( + fakeWindowManager, + lazyViewCapture = lazy { mock() }, + isViewCaptureEnabled = false, + ) + } + var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by Kosmos.Fixture { mockViewCaptureAwareWindowManager } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java index 3041240e8c86..b8be6aa50015 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java @@ -29,6 +29,8 @@ import android.util.ArraySet; import android.util.Log; import android.view.Display; +import androidx.annotation.Nullable; + import com.android.internal.annotations.GuardedBy; import com.android.systemui.res.R; @@ -43,6 +45,9 @@ public class SysuiTestableContext extends TestableContext { private final Map mContextForUser = new HashMap<>(); private final Map mContextForPackage = new HashMap<>(); + @Nullable + private Display mCustomDisplay; + public SysuiTestableContext(Context base) { super(base); setTheme(R.style.Theme_SystemUI); @@ -64,6 +69,18 @@ public class SysuiTestableContext extends TestableContext { return context; } + public void setDisplay(Display display) { + mCustomDisplay = display; + } + + @Override + public Display getDisplay() { + if (mCustomDisplay != null) { + return mCustomDisplay; + } + return super.getDisplay(); + } + public SysuiTestableContext createDefaultDisplayContext() { Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0]; return (SysuiTestableContext) createDisplayContext(display); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt new file mode 100644 index 000000000000..4bcff5577e4b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt @@ -0,0 +1,25 @@ +/* + * 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.decor + +import android.content.testableContext +import com.android.systemui.kosmos.Kosmos + +var Kosmos.privacyDotDecorProviderFactory by + Kosmos.Fixture { + PrivacyDotDecorProviderFactory(testableContext.orCreateTestableResources.resources) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt index 87f7142b8817..ad2654a6b471 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt @@ -29,6 +29,7 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.shade.mockNotificationShadeWindowViewController import com.android.systemui.shade.mockShadeSurface import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository +import com.android.systemui.statusbar.data.repository.privacyDotWindowControllerStore import com.android.systemui.statusbar.data.repository.statusBarModeRepository import com.android.systemui.statusbar.mockNotificationRemoteInputManager import com.android.systemui.statusbar.phone.mockAutoHideController @@ -77,5 +78,6 @@ val Kosmos.multiDisplayStatusBarStarter by statusBarInitializerStore, statusBarWindowControllerStore, statusBarInitializerStore, + privacyDotWindowControllerStore, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt new file mode 100644 index 000000000000..27845aae50dc --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt @@ -0,0 +1,32 @@ +/* + * 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.statusbar.data.repository + +import android.view.Display +import com.android.systemui.statusbar.events.PrivacyDotViewController +import org.mockito.kotlin.mock + +class FakePrivacyDotViewControllerStore : PrivacyDotViewControllerStore { + private val perDisplayMockControllers = mutableMapOf() + + override val defaultDisplay: PrivacyDotViewController + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): PrivacyDotViewController { + return perDisplayMockControllers.computeIfAbsent(displayId) { mock() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt new file mode 100644 index 000000000000..f0aacc0dc9b7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.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.statusbar.data.repository + +import android.view.Display +import com.android.systemui.statusbar.events.PrivacyDotWindowController +import org.mockito.kotlin.mock + +class FakePrivacyDotWindowControllerStore : PrivacyDotWindowControllerStore { + + private val perDisplayMockControllers = mutableMapOf() + + override val defaultDisplay: PrivacyDotWindowController + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): PrivacyDotWindowController { + return perDisplayMockControllers.computeIfAbsent(displayId) { mock() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt new file mode 100644 index 000000000000..3d428a12610c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt @@ -0,0 +1,25 @@ +/* + * 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.statusbar.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakePrivacyDotViewControllerStore by + Kosmos.Fixture { FakePrivacyDotViewControllerStore() } + +var Kosmos.privacyDotViewControllerStore: PrivacyDotViewControllerStore by + Kosmos.Fixture { fakePrivacyDotViewControllerStore } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt new file mode 100644 index 000000000000..aae32cfaafa6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt @@ -0,0 +1,50 @@ +/* + * 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.statusbar.data.repository + +import android.view.WindowManager +import com.android.app.viewcapture.ViewCaptureAwareWindowManager +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.display.data.repository.displayWindowPropertiesRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import org.mockito.kotlin.mock + +val Kosmos.fakePrivacyDotWindowControllerStore by + Kosmos.Fixture { FakePrivacyDotWindowControllerStore() } + +val Kosmos.privacyDotWindowControllerStoreImpl by + Kosmos.Fixture { + PrivacyDotWindowControllerStoreImpl( + backgroundApplicationScope = applicationCoroutineScope, + displayRepository = displayRepository, + windowControllerFactory = { _, _, _, _ -> mock() }, + displayWindowPropertiesRepository = displayWindowPropertiesRepository, + privacyDotViewControllerStore = privacyDotViewControllerStore, + viewCaptureAwareWindowManagerFactory = + object : ViewCaptureAwareWindowManager.Factory { + override fun create( + windowManager: WindowManager + ): ViewCaptureAwareWindowManager { + return mock() + } + }, + ) + } + +var Kosmos.privacyDotWindowControllerStore: PrivacyDotWindowControllerStore by + Kosmos.Fixture { fakePrivacyDotWindowControllerStore } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt new file mode 100644 index 000000000000..53c39a6581d2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt @@ -0,0 +1,63 @@ +/* + * 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.statusbar.events + +import android.view.View + +class FakePrivacyDotViewController : PrivacyDotViewController { + + var topLeft: View? = null + private set + + var topRight: View? = null + private set + + var bottomLeft: View? = null + private set + + var bottomRight: View? = null + private set + + var isInitialized = false + private set + + override fun stop() {} + + override var currentViewState: ViewState = ViewState() + + override var showingListener: PrivacyDotViewController.ShowingListener? = null + + override fun setNewRotation(rot: Int) {} + + override fun hideDotView(dot: View, animate: Boolean) {} + + override fun showDotView(dot: View, animate: Boolean) {} + + override fun updateRotations(rotation: Int, paddingTop: Int) {} + + override fun setCornerSizes(state: ViewState) {} + + override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) { + this.topLeft = topLeft + this.topRight = topRight + this.bottomLeft = bottomLeft + this.bottomRight = bottomRight + isInitialized = true + } + + override fun updateDotView(state: ViewState) {} +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt new file mode 100644 index 000000000000..9cbc9756cf15 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt @@ -0,0 +1,26 @@ +/* + * 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.statusbar.events + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +val Kosmos.mockPrivacyDotViewController by Kosmos.Fixture { mock() } + +val Kosmos.fakePrivacyDotViewController by Kosmos.Fixture { FakePrivacyDotViewController() } + +var Kosmos.privacyDotViewController by Kosmos.Fixture { fakePrivacyDotViewController } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt new file mode 100644 index 000000000000..c73838708a7a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt @@ -0,0 +1,36 @@ +/* + * 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.statusbar.events + +import android.content.testableContext +import android.view.layoutInflater +import com.android.app.viewcapture.realCaptureAwareWindowManager +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.decor.privacyDotDecorProviderFactory +import com.android.systemui.kosmos.Kosmos + +var Kosmos.privacyDotWindowController by + Kosmos.Fixture { + PrivacyDotWindowController( + testableContext.displayId, + privacyDotViewController, + realCaptureAwareWindowManager, + layoutInflater, + fakeExecutor, + privacyDotDecorProviderFactory, + ) + } -- GitLab From 9a6c1872777e4bd392539d77da33c44855e7f3d0 Mon Sep 17 00:00:00 2001 From: David Saff Date: Wed, 30 Oct 2024 14:05:41 -0400 Subject: [PATCH 015/509] Convert AccessibilityQsShortcutsRepositoryImplTest Also introduce Kosmos.collectLastValue Bug: 342622417 Test: local Flag: TEST_ONLY Change-Id: I196fadbb6c62c81e0efdd343901d611a0dfba1ce --- ...essibilityQsShortcutsRepositoryImplTest.kt | 15 +++++----- .../scene/SceneFrameworkIntegrationTest.kt | 29 +++++++++---------- .../android/systemui/kosmos/GeneralKosmos.kt | 4 +++ 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt index 176c3ac43936..2594472a9c8a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt @@ -22,12 +22,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -40,8 +41,6 @@ import org.mockito.junit.MockitoRule @RunWith(AndroidJUnit4::class) class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmos() - private val testDispatcher = kosmos.testDispatcher - private val testScope = kosmos.testScope private val secureSettings = kosmos.fakeSettings @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() @@ -55,8 +54,8 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { return UserA11yQsShortcutsRepository( userId, secureSettings, - testScope.backgroundScope, - testDispatcher, + kosmos.testScope.backgroundScope, + kosmos.testDispatcher, ) } } @@ -69,13 +68,13 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { AccessibilityQsShortcutsRepositoryImpl( a11yManager, userA11yQsShortcutsRepositoryFactory, - testDispatcher + kosmos.testDispatcher, ) } @Test fun a11yQsShortcutTargetsForCorrectUsers() = - testScope.runTest { + kosmos.runTest { val user0 = 0 val targetsForUser0 = setOf("a", "b", "c") val user1 = 1 @@ -94,7 +93,7 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { secureSettings.putStringForUser( SETTING_NAME, a11yQsTargets.joinToString(separator = ":"), - forUser + forUser, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 319f1e577cd9..3be8a380b191 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope @@ -153,7 +154,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() = kosmos.runTest { - val actions by testScope.collectLastValue(kosmos.lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) @@ -170,7 +171,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.runTest { setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) @@ -180,7 +181,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = kosmos.runTest { - val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions) + val actions by collectLastValue(shadeUserActionsViewModel.actions) setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) assertCurrentScene(Scenes.Lockscreen) @@ -197,8 +198,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() = kosmos.runTest { - val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions) - val canSwipeToEnter by testScope.collectLastValue(deviceEntryInteractor.canSwipeToEnter) + val actions by collectLastValue(shadeUserActionsViewModel.actions) + val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter) setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) @@ -279,7 +280,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() = kosmos.runTest { unlockDevice() - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) @@ -302,7 +303,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() = kosmos.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) @@ -319,14 +320,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun bouncerActionButtonClick_opensEmergencyServicesDialer() = kosmos.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) - val bouncerActionButton by - testScope.collectLastValue(bouncerSceneContentViewModel.actionButton) + val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton) assertWithMessage("Bouncer action button not visible") .that(bouncerActionButton) .isNotNull() @@ -341,14 +341,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.runTest { setAuthMethod(AuthenticationMethodModel.Password) startPhoneCall() - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) - val bouncerActionButton by - testScope.collectLastValue(bouncerSceneContentViewModel.actionButton) + val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton) assertWithMessage("Bouncer action button not visible during call") .that(bouncerActionButton) .isNotNull() @@ -574,7 +573,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(getCurrentSceneInUi()) .isEqualTo(Scenes.Bouncer) val authMethodViewModel by - testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) + collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) .isInstanceOf(PinBouncerViewModel::class.java) @@ -603,7 +602,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(getCurrentSceneInUi()) .isEqualTo(Scenes.Bouncer) val authMethodViewModel by - testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) + collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) .isInstanceOf(PinBouncerViewModel::class.java) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index ddae58168201..72cb1dfe38db 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -1,8 +1,10 @@ package com.android.systemui.kosmos import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos.Fixture import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -45,3 +47,5 @@ fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) = testScope.runTest { this@runTest.testBody() } fun Kosmos.runCurrent() = testScope.runCurrent() + +fun Kosmos.collectLastValue(flow: Flow) = testScope.collectLastValue(flow) -- GitLab From 9fd37904001b479034494c0139ae334a0cad54eb Mon Sep 17 00:00:00 2001 From: Alex Stetson Date: Wed, 30 Oct 2024 14:17:44 -0700 Subject: [PATCH 016/509] Override RoleManager for KeyGestureEventTests It's possible that the device may not have defaults or certain roles supported. To still allow these tests to run properly, the role manager should be overridden. Bug: 364802899 Test: atest com.android.server.policy.KeyGestureEventTests Flag: TEST_ONLY Change-Id: I803d590ec5bbf0fb85c32957fbd259693c3463b8 --- .../src/com/android/server/policy/KeyGestureEventTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index ad11c2681779..f0b1c5c4705e 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -331,6 +331,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideTogglePanel(); mPhoneWindowManager.overrideInjectKeyEvent(); + mPhoneWindowManager.overrideRoleManager(); } @Test -- GitLab From 74cda91f692a3d979e24e847ac5056d4a14f9c11 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Kushwah Date: Mon, 28 Oct 2024 11:07:20 +0530 Subject: [PATCH 017/509] Add OemExtension callbacks support To get NFC state updates to registered Apps - EE Activated/Deactivated Bug: 329043522 Test: Manual test discovery on/off, RF Field ON/OFF, CE Activated or Not Change-Id: I4363c6eb69e12cb3a867a0fd50e933514f2f4f6b --- nfc/api/system-current.txt | 1 + .../android/nfc/INfcOemExtensionCallback.aidl | 1 + nfc/java/android/nfc/NfcOemExtension.java | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 24e14e69637b..8d1df4be3ecd 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -91,6 +91,7 @@ package android.nfc { method public void onDisable(@NonNull java.util.function.Consumer); method public void onDisableFinished(int); method public void onDisableStarted(); + method public void onEeListenActivated(boolean); method public void onEnable(@NonNull java.util.function.Consumer); method public void onEnableFinished(int); method public void onEnableStarted(); diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl index 48c7ee659266..28ea7b0c5ee1 100644 --- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl +++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl @@ -46,6 +46,7 @@ interface INfcOemExtensionCallback { void onCardEmulationActivated(boolean isActivated); void onRfFieldActivated(boolean isActivated); void onRfDiscoveryStarted(boolean isDiscoveryStarted); + void onEeListenActivated(boolean isActivated); void onGetOemAppSearchIntent(in List firstPackage, in ResultReceiver intentConsumer); void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent); void onLaunchHceAppChooserActivity(in String selectedAid, in List services, in ComponentName failedComponent, in String category); diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index 520ba896f01f..43598c99bfe3 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -80,6 +80,7 @@ public final class NfcOemExtension { private boolean mCardEmulationActivated = false; private boolean mRfFieldActivated = false; private boolean mRfDiscoveryStarted = false; + private boolean mEeListenActivated = false; /** * Mode Type for {@link #setControllerAlwaysOnMode(int)}. @@ -319,6 +320,13 @@ public final class NfcOemExtension { */ void onRfDiscoveryStarted(boolean isDiscoveryStarted); + /** + * Notifies the NFCEE (NFC Execution Environment) Listen has been activated. + * + * @param isActivated true, if EE Listen is ON, else EE Listen is OFF. + */ + void onEeListenActivated(boolean isActivated); + /** * Gets the intent to find the OEM package in the OEM App market. If the consumer returns * {@code null} or a timeout occurs, the intent from the first available package will be @@ -430,6 +438,7 @@ public final class NfcOemExtension { callback.onCardEmulationActivated(mCardEmulationActivated); callback.onRfFieldActivated(mRfFieldActivated); callback.onRfDiscoveryStarted(mRfDiscoveryStarted); + callback.onEeListenActivated(mEeListenActivated); }); } } @@ -703,6 +712,13 @@ public final class NfcOemExtension { handleVoidCallback(isDiscoveryStarted, cb::onRfDiscoveryStarted, ex)); } + @Override + public void onEeListenActivated(boolean isActivated) throws RemoteException { + mEeListenActivated = isActivated; + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(isActivated, cb::onEeListenActivated, ex)); + } + @Override public void onStateUpdated(int state) throws RemoteException { mCallbackMap.forEach((cb, ex) -> -- GitLab From 78f5213f2ecece9605ffbc8432abcd256530e848 Mon Sep 17 00:00:00 2001 From: Yurii Zubrytskyi Date: Wed, 30 Oct 2024 16:06:43 -0700 Subject: [PATCH 018/509] [res] Allow changing RRO state that target android 'android' resources don't have overlayable definition, so there's no way to enable or disable them for the apps that aren't root or system. This isn't intentional, and the CL enables it for the apps with the proper permission. Bug: 364035303 Flag: android.content.res.rro_control_for_android_no_overlayable Test: atest CtsHandleConfigChangeHostTests Change-Id: I6894f1d073f9e50eaaa70675ab9372dca1de5fdf --- .../java/com/android/server/om/OverlayActorEnforcer.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index 015b7fd74211..38f39393a025 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -19,6 +19,7 @@ package com.android.server.om; import android.annotation.NonNull; import android.content.om.OverlayInfo; import android.content.om.OverlayableInfo; +import android.content.res.Flags; import android.net.Uri; import android.os.Process; import android.text.TextUtils; @@ -162,11 +163,15 @@ public class OverlayActorEnforcer { return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE; } - if (targetOverlayable == null) { + // Framework doesn't have declaration by design, and we still want to be able + // to enable its overlays from the packages with the permission. + if (targetOverlayable == null + && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals( + "android"))) { return ActorState.MISSING_OVERLAYABLE; } - String actor = targetOverlayable.actor; + final String actor = targetOverlayable == null ? null : targetOverlayable.actor; if (TextUtils.isEmpty(actor)) { // If there's no actor defined, fallback to the legacy permission check try { -- GitLab From b80c7c641bb43b6a5bf4651765e198302709788b Mon Sep 17 00:00:00 2001 From: TYM Tsai Date: Mon, 7 Oct 2024 21:47:18 +0800 Subject: [PATCH 019/509] Hold a wakelock for the installation Installation makes no progress during screen off because the phone suspends, so a wake lock is held to avoid becoming suspended. Bug: 369147361 Flag: EXEMPT bug fix Test: atest CtsInstallHostTestCases Change-Id: Ia45564448268edeffee1544eb6a6cb1677c209fa --- .../server/pm/InstallPackageHelper.java | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 83292b775ddc..1f34597699b2 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -85,6 +85,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX; import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN; import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE; import static com.android.server.pm.PackageManagerService.TAG; +import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT; import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures; import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures; import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists; @@ -133,9 +134,11 @@ import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SELinux; +import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -174,7 +177,6 @@ import com.android.internal.util.CollectionUtils; import com.android.server.EventLogTags; import com.android.server.SystemConfig; import com.android.server.criticalevents.CriticalEventLog; -import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; @@ -210,6 +212,10 @@ import java.util.concurrent.ExecutorService; final class InstallPackageHelper { + // One minute over PM WATCHDOG_TIMEOUT + private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60; + private static final String INSTALLER_WAKE_LOCK_TAG = "installer:packages"; + private final PackageManagerService mPm; private final AppDataHelper mAppDataHelper; private final BroadcastHelper mBroadcastHelper; @@ -218,14 +224,16 @@ final class InstallPackageHelper { private final IncrementalManager mIncrementalManager; private final ApexManager mApexManager; private final DexManager mDexManager; - private final ArtManagerService mArtManagerService; private final Context mContext; - private final PackageDexOptimizer mPackageDexOptimizer; private final PackageAbiHelper mPackageAbiHelper; private final SharedLibrariesImpl mSharedLibraries; private final PackageManagerServiceInjector mInjector; private final UpdateOwnershipHelper mUpdateOwnershipHelper; + private final Object mInternalLock = new Object(); + @GuardedBy("mInternalLock") + private PowerManager.WakeLock mInstallingWakeLock; + // TODO(b/198166813): remove PMS dependency InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper, @@ -241,9 +249,7 @@ final class InstallPackageHelper { mIncrementalManager = pm.mInjector.getIncrementalManager(); mApexManager = pm.mInjector.getApexManager(); mDexManager = pm.mInjector.getDexManager(); - mArtManagerService = pm.mInjector.getArtManagerService(); mContext = pm.mInjector.getContext(); - mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer(); mPackageAbiHelper = pm.mInjector.getAbiHelper(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); @@ -1013,6 +1019,7 @@ final class InstallPackageHelper { boolean success = false; final Map createdAppId = new ArrayMap<>(requests.size()); final Map versionInfos = new ArrayMap<>(requests.size()); + final long acquireTime = acquireWakeLock(requests.size()); try { CriticalEventLog.getInstance().logInstallPackagesStarted(); if (prepareInstallPackages(requests) @@ -1033,6 +1040,46 @@ final class InstallPackageHelper { } finally { completeInstallProcess(requests, createdAppId, success); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + releaseWakeLock(acquireTime, requests.size()); + } + } + + private long acquireWakeLock(int count) { + if (!mPm.isSystemReady()) { + return -1; + } + synchronized (mInternalLock) { + if (mInstallingWakeLock == null) { + PowerManager pwm = mContext.getSystemService(PowerManager.class); + if (pwm != null) { + mInstallingWakeLock = pwm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + INSTALLER_WAKE_LOCK_TAG); + } else { + Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock"); + return -1; + } + } + + mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count); + return SystemClock.elapsedRealtime(); + } + } + + private void releaseWakeLock(final long acquireTime, int count) { + if (acquireTime < 0) { + return; + } + synchronized (mInternalLock) { + try { + if (mInstallingWakeLock == null) { + return; + } + if (mInstallingWakeLock.isHeld()) { + mInstallingWakeLock.release(); + } + } catch (RuntimeException e) { + Slog.wtf(TAG, "Error while releasing installer lock", e); + } } } -- GitLab From fcc2e1173228d25a08ff27c5f88a0ad89cc270fc Mon Sep 17 00:00:00 2001 From: Brad Lassey Date: Tue, 29 Oct 2024 15:17:13 -0400 Subject: [PATCH 020/509] Implement Observe Mode and Preferred Service event listeners with functional callbacks. Bug: 356447790 Test: CTS tests Flag: android.nfc.Flags.nfcEventListener Change-Id: I66acbe5d1e41f30149e98f13d2374841d4b1604d --- nfc/api/current.txt | 9 +- .../android/nfc/ComponentNameAndUser.aidl | 19 +++ .../android/nfc/ComponentNameAndUser.java | 100 ++++++++++++++++ nfc/java/android/nfc/INfcCardEmulation.aidl | 5 + nfc/java/android/nfc/INfcEventListener.aidl | 11 ++ .../nfc/cardemulation/CardEmulation.java | 109 ++++++++++++++++++ .../nfc/cardemulation/HostApduService.java | 40 ------- 7 files changed, 251 insertions(+), 42 deletions(-) create mode 100644 nfc/java/android/nfc/ComponentNameAndUser.aidl create mode 100644 nfc/java/android/nfc/ComponentNameAndUser.java create mode 100644 nfc/java/android/nfc/INfcEventListener.aidl diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 96b7c1339190..008120429c40 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -207,6 +207,7 @@ package android.nfc.cardemulation { method public boolean isDefaultServiceForCategory(android.content.ComponentName, String); method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported(); method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List); + method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); method public boolean removeAidsForService(android.content.ComponentName, String); @@ -216,6 +217,7 @@ package android.nfc.cardemulation { method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean); method public boolean supportsAidPrefixRegistration(); + method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; @@ -233,13 +235,16 @@ package android.nfc.cardemulation { field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 } + @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener { + method @FlaggedApi("android.nfc.nfc_event_listener") public default void onObserveModeStateChanged(boolean); + method @FlaggedApi("android.nfc.nfc_event_listener") public default void onPreferredServiceChanged(boolean); + } + public abstract class HostApduService extends android.app.Service { ctor public HostApduService(); method public final void notifyUnhandled(); method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); - method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean); - method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean); method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List); method public final void sendResponseApdu(byte[]); diff --git a/nfc/java/android/nfc/ComponentNameAndUser.aidl b/nfc/java/android/nfc/ComponentNameAndUser.aidl new file mode 100644 index 000000000000..e677998a7970 --- /dev/null +++ b/nfc/java/android/nfc/ComponentNameAndUser.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +parcelable ComponentNameAndUser; \ No newline at end of file diff --git a/nfc/java/android/nfc/ComponentNameAndUser.java b/nfc/java/android/nfc/ComponentNameAndUser.java new file mode 100644 index 000000000000..59e6c62926c9 --- /dev/null +++ b/nfc/java/android/nfc/ComponentNameAndUser.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * @hide + */ +public class ComponentNameAndUser implements Parcelable { + @UserIdInt private final int mUserId; + private ComponentName mComponentName; + + public ComponentNameAndUser(@UserIdInt int userId, ComponentName componentName) { + mUserId = userId; + mComponentName = componentName; + } + + /** + * @hide + */ + public int describeContents() { + return 0; + } + + /** + * @hide + */ + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mUserId); + out.writeParcelable(mComponentName, flags); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public ComponentNameAndUser createFromParcel(Parcel in) { + return new ComponentNameAndUser(in); + } + + public ComponentNameAndUser[] newArray(int size) { + return new ComponentNameAndUser[size]; + } + }; + + private ComponentNameAndUser(Parcel in) { + mUserId = in.readInt(); + mComponentName = in.readParcelable(null, ComponentName.class); + } + + @UserIdInt + public int getUserId() { + return mUserId; + } + + public ComponentName getComponentName() { + return mComponentName; + } + + @Override + public String toString() { + return mComponentName + " for user id: " + mUserId; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof ComponentNameAndUser) { + ComponentNameAndUser other = (ComponentNameAndUser) obj; + return other.getUserId() == mUserId + && Objects.equals(other.getComponentName(), mComponentName); + } + return false; + } + + @Override + public int hashCode() { + if (mComponentName == null) { + return mUserId; + } + return mComponentName.hashCode() + mUserId; + } +} diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index 8535e4a9cfd2..5e2e92d958a4 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -17,6 +17,8 @@ package android.nfc; import android.content.ComponentName; +import android.nfc.INfcEventListener; + import android.nfc.cardemulation.AidGroup; import android.nfc.cardemulation.ApduServiceInfo; import android.os.RemoteCallback; @@ -55,4 +57,7 @@ interface INfcCardEmulation boolean isAutoChangeEnabled(); List getRoutingStatus(); void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc); + + void registerNfcEventListener(in INfcEventListener listener); + void unregisterNfcEventListener(in INfcEventListener listener); } diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventListener.aidl new file mode 100644 index 000000000000..5162c26ac536 --- /dev/null +++ b/nfc/java/android/nfc/INfcEventListener.aidl @@ -0,0 +1,11 @@ +package android.nfc; + +import android.nfc.ComponentNameAndUser; + +/** + * @hide + */ +oneway interface INfcEventListener { + void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser); + void onObserveModeStateChanged(boolean isEnabled); +} \ No newline at end of file diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index d8f04c50b695..eb28c3b9c930 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -17,6 +17,7 @@ package android.nfc.cardemulation; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -33,15 +34,18 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.nfc.ComponentNameAndUser; import android.nfc.Constants; import android.nfc.Flags; import android.nfc.INfcCardEmulation; +import android.nfc.INfcEventListener; import android.nfc.NfcAdapter; import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; +import android.util.ArrayMap; import android.util.Log; import java.lang.annotation.Retention; @@ -50,6 +54,8 @@ import java.util.HashMap; import java.util.HexFormat; import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.Executor; import java.util.regex.Pattern; /** @@ -1076,4 +1082,107 @@ public final class CardEmulation { default -> throw new IllegalStateException("Unexpected value: " + route); }; } + + /** Listener for preferred service state changes. */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public interface NfcEventListener { + /** + * This method is called when this package gains or loses preferred Nfc service status, + * either the Default Wallet Role holder (see {@link + * android.app.role.RoleManager#ROLE_WALLET}) or the preferred service of the foreground + * activity set with {@link #setPreferredService(Activity, ComponentName)} + * + * @param isPreferred true is this service has become the preferred Nfc service, false if it + * is no longer the preferred service + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + default void onPreferredServiceChanged(boolean isPreferred) {} + + /** + * This method is called when observe mode has been enabled or disabled. + * + * @param isEnabled true if observe mode has been enabled, false if it has been disabled + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + default void onObserveModeStateChanged(boolean isEnabled) {} + } + + private final ArrayMap mNfcEventListeners = new ArrayMap<>(); + + final INfcEventListener mINfcEventListener = + new INfcEventListener.Stub() { + public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + boolean isPreferred = + componentNameAndUser != null + && componentNameAndUser.getUserId() + == mContext.getUser().getIdentifier() + && componentNameAndUser.getComponentName() != null + && Objects.equals( + mContext.getPackageName(), + componentNameAndUser.getComponentName() + .getPackageName()); + synchronized (mNfcEventListeners) { + mNfcEventListeners.forEach( + (listener, executor) -> { + executor.execute( + () -> listener.onPreferredServiceChanged(isPreferred)); + }); + } + } + + public void onObserveModeStateChanged(boolean isEnabled) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + synchronized (mNfcEventListeners) { + mNfcEventListeners.forEach( + (listener, executor) -> { + executor.execute( + () -> listener.onObserveModeStateChanged(isEnabled)); + }); + } + } + }; + + /** + * Register a listener for NFC Events. + * + * @param executor The Executor to run the call back with + * @param listener The listener to register + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public void registerNfcEventListener( + @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + synchronized (mNfcEventListeners) { + mNfcEventListeners.put(listener, executor); + if (mNfcEventListeners.size() == 1) { + callService(() -> sService.registerNfcEventListener(mINfcEventListener)); + } + } + } + + /** + * Unregister a preferred service listener that was previously registered with {@link + * #registerNfcEventListener(Executor, NfcEventListener)} + * + * @param listener The previously registered listener to unregister + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public void unregisterNfcEventListener(@NonNull NfcEventListener listener) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + synchronized (mNfcEventListeners) { + mNfcEventListeners.remove(listener); + if (mNfcEventListeners.size() == 0) { + callService(() -> sService.unregisterNfcEventListener(mINfcEventListener)); + } + } + } } diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java index cd8e19c54565..4f601f0704b4 100644 --- a/nfc/java/android/nfc/cardemulation/HostApduService.java +++ b/nfc/java/android/nfc/cardemulation/HostApduService.java @@ -239,15 +239,6 @@ public abstract class HostApduService extends Service { */ public static final int MSG_POLLING_LOOP = 4; - /** - * @hide - */ - public static final int MSG_OBSERVE_MODE_CHANGE = 5; - - /** - * @hide - */ - public static final int MSG_PREFERRED_SERVICE_CHANGED = 6; /** * @hide @@ -343,16 +334,6 @@ public abstract class HostApduService extends Service { processPollingFrames(pollingFrames); } break; - case MSG_OBSERVE_MODE_CHANGE: - if (android.nfc.Flags.nfcEventListener()) { - onObserveModeStateChanged(msg.arg1 == 1); - } - break; - case MSG_PREFERRED_SERVICE_CHANGED: - if (android.nfc.Flags.nfcEventListener()) { - onPreferredServiceChanged(msg.arg1 == 1); - } - break; default: super.handleMessage(msg); } @@ -462,25 +443,4 @@ public abstract class HostApduService extends Service { */ public abstract void onDeactivated(int reason); - - /** - * This method is called when this service is the preferred Nfc service and - * Observe mode has been enabled or disabled. - * - * @param isEnabled true if observe mode has been enabled, false if it has been disabled - */ - @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) - public void onObserveModeStateChanged(boolean isEnabled) { - - } - - /** - * This method is called when this service gains or loses preferred Nfc service status. - * - * @param isPreferred true is this service has become the preferred Nfc service, - * false if it is no longer the preferred service - */ - @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) - public void onPreferredServiceChanged(boolean isPreferred) { - } } -- GitLab From df7c1e93cc119e8587d02b94f1c2c9f14d91a35c Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Tue, 29 Oct 2024 07:25:08 +0000 Subject: [PATCH 021/509] Ensure the top-resumed-activity is updated after WCT applied The Recents activity was resumed while applying WCT, and the top-resumed-activity change was not reported after commit 171d4ad. Bug: 376184135 Test: Swipe up to Recents Flag: EXEMPT bugfix Change-Id: I5ed3885b47b7f1f9fe003011ea265942251279a9 --- .../java/com/android/server/wm/WindowOrganizerController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 166d74b132bd..dac8f69a4cae 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -813,6 +813,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); if (deferResume) { mService.mTaskSupervisor.endDeferResume(); + // Transient launching the Recents via HIERARCHY_OP_TYPE_PENDING_INTENT directly + // resume the Recents activity with no TRANSACT_EFFECTS_LIFECYCLE. Explicitly + // checks if the top resumed activity should be updated after defer-resume ended. + mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT"); } mService.continueWindowLayout(); } -- GitLab From dde9ccdcd56e1e50273f2b07f820798f313bc2ce Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Thu, 31 Oct 2024 02:49:10 +0000 Subject: [PATCH 022/509] Add aspect ratio setting button in window handle menu The button will be visible if the task is fullscreen windowing mode and the display is ignore-orientation-request (i.e. large screen). Currently it only applies if desktop mode is enabled. This is to make it easier for users to switch aspect ratio of app. Bug: 376395315 Flag: EXEMPT add a common button Test: adb shell setprop \ persist.wm.debug.desktop_mode_enforce_device_restrictions false adb shell settings put global enable_freeform_support 1 adb shell settings put global override_desktop_mode_features 1 adb shell cmd window set-ignore-orientation-request 1 adb reboot Open an app in fullscreen mode. Click top bar to see the button. Change-Id: I5908286624cd426daeca317e9803e6619fe994be --- ...ode_ic_handle_menu_change_aspect_ratio.xml | 25 ++++++ .../desktop_mode_window_decor_handle_menu.xml | 8 ++ libs/WindowManager/Shell/res/values/dimen.xml | 8 +- .../Shell/res/values/strings.xml | 2 + .../wm/shell/compatui/CompatUIController.java | 8 +- .../DesktopModeWindowDecorViewModel.java | 5 ++ .../DesktopModeWindowDecoration.java | 10 +++ .../wm/shell/windowdecor/HandleMenu.kt | 87 +++++++++++++------ .../DesktopModeWindowDecorationTests.java | 10 ++- .../wm/shell/windowdecor/HandleMenuTest.kt | 2 + 10 files changed, 131 insertions(+), 34 deletions(-) create mode 100644 libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml new file mode 100644 index 000000000000..4442e9df7688 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index 5609663c01a0..f90e165ffc74 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -157,6 +157,14 @@ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_manage_windows" android:drawableTint="?androidprv:attr/materialColorOnSurface" style="@style/DesktopModeHandleMenuActionButton" /> + +