diff options
95 files changed, 2475 insertions, 511 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 3bddfe175c0d..109170615c94 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12319,6 +12319,7 @@ package android.content.pm { method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); + method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean); method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle); method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); @@ -25477,34 +25478,34 @@ package android.media.audiofx { field public short preset; } - public class Virtualizer extends android.media.audiofx.AudioEffect { - ctor public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException; - method public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public boolean getStrengthSupported(); - method public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener); - method public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - field public static final int PARAM_STRENGTH = 1; // 0x1 - field public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0 - field public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1 - field public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2 - field public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0 - field public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3 - } - - public static interface Virtualizer.OnParameterChangeListener { - method public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short); - } - - public static class Virtualizer.Settings { - ctor public Virtualizer.Settings(); - ctor public Virtualizer.Settings(String); - field public short strength; + @Deprecated public class Virtualizer extends android.media.audiofx.AudioEffect { + ctor @Deprecated public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean getStrengthSupported(); + method @Deprecated public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener); + method @Deprecated public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + field @Deprecated public static final int PARAM_STRENGTH = 1; // 0x1 + field @Deprecated public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0 + field @Deprecated public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1 + field @Deprecated public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2 + field @Deprecated public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0 + field @Deprecated public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3 + } + + @Deprecated public static interface Virtualizer.OnParameterChangeListener { + method @Deprecated public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short); + } + + @Deprecated public static class Virtualizer.Settings { + ctor @Deprecated public Virtualizer.Settings(); + ctor @Deprecated public Virtualizer.Settings(String); + field @Deprecated public short strength; } public class Visualizer { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index d8d136ae4df9..ea37e7fbcce1 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1548,9 +1548,24 @@ public class AppOpsManager { public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER = AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER; + /** + * Whether the app has enabled to receive the icon overlay for fetching archived apps. + * + * @hide + */ + public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY; + + /** + * Whether the app has enabled compatibility support for unarchival. + * + * @hide + */ + public static final int OP_UNARCHIVAL_CONFIRMATION = + AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 144; + public static final int _NUM_OP = 146; /** * All app ops represented as strings. @@ -1700,6 +1715,8 @@ public class AppOpsManager { OPSTR_ENABLE_MOBILE_DATA_BY_USER, OPSTR_RESERVED_FOR_TESTING, OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, + OPSTR_ARCHIVE_ICON_OVERLAY, + OPSTR_UNARCHIVAL_CONFIRMATION, }) public @interface AppOpString {} @@ -2040,6 +2057,20 @@ public class AppOpsManager { public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control"; /** + * Whether the app has enabled to receive the icon overlay for fetching archived apps. + * + * @hide + */ + public static final String OPSTR_ARCHIVE_ICON_OVERLAY = "android:archive_icon_overlay"; + + /** + * Whether the app has enabled compatibility support for unarchival. + * + * @hide + */ + public static final String OPSTR_UNARCHIVAL_CONFIRMATION = "android:unarchival_support"; + + /** * AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage} * * <p>MediaProvider is the only component (outside of system server) that should care about this @@ -2504,6 +2535,8 @@ public class AppOpsManager { OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, OP_MEDIA_ROUTING_CONTROL, OP_READ_SYSTEM_GRAMMATICAL_GENDER, + OP_ARCHIVE_ICON_OVERLAY, + OP_UNARCHIVAL_CONFIRMATION, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2960,6 +2993,12 @@ public class AppOpsManager { OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER") .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER) .build(), + new AppOpInfo.Builder(OP_ARCHIVE_ICON_OVERLAY, OPSTR_ARCHIVE_ICON_OVERLAY, + "ARCHIVE_ICON_OVERLAY") + .setDefaultMode(MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_UNARCHIVAL_CONFIRMATION, OPSTR_UNARCHIVAL_CONFIRMATION, + "UNARCHIVAL_CONFIRMATION") + .setDefaultMode(MODE_ALLOWED).build(), }; // The number of longs needed to form a full bitmask of app ops @@ -3094,7 +3133,7 @@ public class AppOpsManager { /** * Retrieve the permission associated with an operation, or null if there is not one. - * + * @param op The operation name. * * @hide diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 34c44f9489d5..4f1db7d3784a 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -4032,7 +4032,8 @@ public class ApplicationPackageManager extends PackageManager { private Drawable getArchivedAppIcon(String packageName) { try { return new BitmapDrawable(null, - mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()))); + mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()), + mContext.getPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index afa513dbaaef..c6712c044539 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -413,7 +413,9 @@ public final class ApplicationStartInfo implements Parcelable { * @hide */ public void setIntent(Intent startIntent) { - mStartIntent = startIntent; + if (startIntent != null) { + mStartIntent = startIntent.maybeStripForHistory(); + } } /** @@ -548,6 +550,8 @@ public final class ApplicationStartInfo implements Parcelable { /** * The intent used to launch the application. * + * <p class="note"> Note: Intent is stripped and does not include extras.</p> + * * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p> */ @SuppressLint("IntentBuilderName") @@ -662,6 +666,7 @@ public final class ApplicationStartInfo implements Parcelable { private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp"; private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key"; private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts"; + private static final String PROTO_SERIALIZER_ATTRIBUTE_INTENT = "intent"; /** * Write to a protocol buffer output stream. Protocol buffer message definition at {@link @@ -702,10 +707,17 @@ public final class ApplicationStartInfo implements Parcelable { } proto.write(ApplicationStartInfoProto.START_TYPE, mStartType); if (mStartIntent != null) { - Parcel parcel = Parcel.obtain(); - mStartIntent.writeToParcel(parcel, 0); - proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall()); - parcel.recycle(); + ByteArrayOutputStream intentBytes = new ByteArrayOutputStream(); + ObjectOutputStream intentOut = new ObjectOutputStream(intentBytes); + TypedXmlSerializer serializer = Xml.resolveSerializer(intentOut); + serializer.startDocument(null, true); + serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT); + mStartIntent.saveToXml(serializer); + serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT); + serializer.endDocument(); + proto.write(ApplicationStartInfoProto.START_INTENT, + intentBytes.toByteArray()); + intentOut.close(); } proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); proto.end(token); @@ -772,15 +784,17 @@ public final class ApplicationStartInfo implements Parcelable { mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE); break; case (int) ApplicationStartInfoProto.START_INTENT: - byte[] startIntentBytes = proto.readBytes( - ApplicationStartInfoProto.START_INTENT); - if (startIntentBytes.length > 0) { - Parcel parcel = Parcel.obtain(); - parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length); - parcel.setDataPosition(0); - mStartIntent = Intent.CREATOR.createFromParcel(parcel); - parcel.recycle(); + ByteArrayInputStream intentBytes = new ByteArrayInputStream(proto.readBytes( + ApplicationStartInfoProto.START_INTENT)); + ObjectInputStream intentIn = new ObjectInputStream(intentBytes); + try { + TypedXmlPullParser parser = Xml.resolvePullParser(intentIn); + XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_INTENT); + mStartIntent = Intent.restoreFromXml(parser); + } catch (XmlPullParserException e) { + // Intent lost } + intentIn.close(); break; case (int) ApplicationStartInfoProto.LAUNCH_MODE: mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE); diff --git a/core/java/android/content/pm/ArchivedActivityInfo.java b/core/java/android/content/pm/ArchivedActivityInfo.java index 1faa4373d88f..166d26503625 100644 --- a/core/java/android/content/pm/ArchivedActivityInfo.java +++ b/core/java/android/content/pm/ArchivedActivityInfo.java @@ -91,26 +91,31 @@ public final class ArchivedActivityInfo { * @hide */ public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) { - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); - - } - Bitmap bitmap; - if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { - // Needed for drawables that are just a single color. - bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); } else { - bitmap = + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + // Needed for drawables that are just a single color. + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } else { + bitmap = Bitmap.createBitmap( - drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), - Bitmap.Config.ARGB_8888); + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); } - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) { + if (iconSize <= 0) { + return bitmap; + } + + if (bitmap.getWidth() < iconSize || bitmap.getHeight() < iconSize + || bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) { var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true); if (scaledBitmap != bitmap) { bitmap.recycle(); @@ -235,7 +240,7 @@ public final class ArchivedActivityInfo { } @DataClass.Generated( - time = 1698789991876L, + time = 1705615445673L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivityInfo.java", inputSignatures = "private @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)") diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index a97de6368b8c..62db65f15df3 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -128,4 +128,6 @@ interface ILauncherApps { /** Unregister a callback, so that it won't be called when LauncherApps dumps. */ void unRegisterDumpCallback(IDumpCallback cb); + + void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 6dc8d4738c87..380de965b143 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -840,7 +840,7 @@ interface IPackageManager { ArchivedPackageParcel getArchivedPackage(in String packageName, int userId); - Bitmap getArchivedAppIcon(String packageName, in UserHandle user); + Bitmap getArchivedAppIcon(String packageName, in UserHandle user, String callingPackageName); boolean isAppArchivable(String packageName, in UserHandle user); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 1d2b1aff46bc..50be983ec938 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1801,6 +1801,31 @@ public class LauncherApps { } } + /** + * Enable or disable different archive compatibility options of the launcher. + * + * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware + * that a certain app is archived. True by default. + * Launchers might want to disable this operation if they want to provide custom user experience + * to differentiate archived apps. + * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when + * they click an archived app, which explains that the app will be downloaded and restored in + * the background. True by default. + * Launchers might want to disable this operation if they provide sufficient, alternative user + * guidance to highlight that an unarchival is starting and ongoing once an archived app is + * tapped. E.g., this could be achieved by showing the unarchival progress around the icon. + */ + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) + public void setArchiveCompatibilityOptions(boolean enableIconOverlay, + boolean enableUnarchivalConfirmation) { + try { + mService.setArchiveCompatibilityOptions(enableIconOverlay, + enableUnarchivalConfirmation); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** @return position in mCallbacks for callback or -1 if not present. */ private int findCallbackLocked(Callback callback) { if (callback == null) { diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 8a4f678b52f2..35ae3c9e23c7 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -40,12 +40,15 @@ import android.os.Looper; import android.os.OperationCanceledException; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Pair; import android.util.Printer; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.NeverCompile; @@ -103,8 +106,14 @@ public final class SQLiteDatabase extends SQLiteClosable { // Stores reference to all databases opened in the current process. // (The referent Object is not used at this time.) // INVARIANT: Guarded by sActiveDatabases. + @GuardedBy("sActiveDatabases") private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>(); + // Tracks which database files are currently open. If a database file is opened more than + // once at any given moment, the associated databases are marked as "concurrent". + @GuardedBy("sActiveDatabases") + private static final OpenTracker sOpenTracker = new OpenTracker(); + // Thread-local for database sessions that belong to this database. // Each thread has its own database session. // INVARIANT: Immutable. @@ -510,6 +519,7 @@ public final class SQLiteDatabase extends SQLiteClosable { private void dispose(boolean finalized) { final SQLiteConnectionPool pool; + final String path; synchronized (mLock) { if (mCloseGuardLocked != null) { if (finalized) { @@ -520,10 +530,12 @@ public final class SQLiteDatabase extends SQLiteClosable { pool = mConnectionPoolLocked; mConnectionPoolLocked = null; + path = isInMemoryDatabase() ? null : getPath(); } if (!finalized) { synchronized (sActiveDatabases) { + sOpenTracker.close(path); sActiveDatabases.remove(this); } @@ -1132,6 +1144,74 @@ public final class SQLiteDatabase extends SQLiteClosable { } } + /** + * Track the number of times a database file has been opened. There is a primary connection + * associated with every open database, and these can contend with each other, leading to + * unexpected SQLiteDatabaseLockedException exceptions. The tracking here is only advisory: + * multiply-opened databases are logged but no other action is taken. + * + * This class is not thread-safe. + */ + private static class OpenTracker { + // The list of currently-open databases. This maps the database file to the number of + // currently-active opens. + private final ArrayMap<String, Integer> mOpens = new ArrayMap<>(); + + // The maximum number of concurrently open database paths that will be stored. Once this + // many paths have been recorded, further paths are logged but not saved. + private static final int MAX_RECORDED_PATHS = 20; + + // The list of databases that were ever concurrently opened. + private final ArraySet<String> mConcurrent = new ArraySet<>(); + + /** Return the canonical path. On error, just return the input path. */ + private static String normalize(String path) { + try { + return new File(path).toPath().toRealPath().toString(); + } catch (Exception e) { + // If there is an IO or security exception, just continue, using the input path. + return path; + } + } + + /** Return true if the path is currently open in another SQLiteDatabase instance. */ + void open(@Nullable String path) { + if (path == null) return; + path = normalize(path); + + Integer count = mOpens.get(path); + if (count == null || count == 0) { + mOpens.put(path, 1); + return; + } else { + mOpens.put(path, count + 1); + if (mConcurrent.size() < MAX_RECORDED_PATHS) { + mConcurrent.add(path); + } + Log.w(TAG, "multiple primary connections on " + path); + return; + } + } + + void close(@Nullable String path) { + if (path == null) return; + path = normalize(path); + Integer count = mOpens.get(path); + if (count == null || count <= 0) { + Log.e(TAG, "open database counting failure on " + path); + } else if (count == 1) { + // Implicitly set the count to zero, and make mOpens smaller. + mOpens.remove(path); + } else { + mOpens.put(path, count - 1); + } + } + + ArraySet<String> getConcurrentDatabasePaths() { + return new ArraySet<>(mConcurrent); + } + } + private void open() { try { try { @@ -1153,14 +1233,17 @@ public final class SQLiteDatabase extends SQLiteClosable { } private void openInner() { + final String path; synchronized (mLock) { assert mConnectionPoolLocked == null; mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked); mCloseGuardLocked.open("close"); + path = isInMemoryDatabase() ? null : getPath(); } synchronized (sActiveDatabases) { sActiveDatabases.put(this, null); + sOpenTracker.open(path); } } @@ -2345,6 +2428,17 @@ public final class SQLiteDatabase extends SQLiteClosable { } /** + * Return list of databases that have been concurrently opened. + * @hide + */ + @VisibleForTesting + public static ArraySet<String> getConcurrentDatabasePaths() { + synchronized (sActiveDatabases) { + return sOpenTracker.getConcurrentDatabasePaths(); + } + } + + /** * Returns true if the new version code is greater than the current database version. * * @param newVersion The new version code. @@ -2766,6 +2860,19 @@ public final class SQLiteDatabase extends SQLiteClosable { dumpDatabaseDirectory(printer, new File(dir), isSystem); } } + + // Dump concurrently-opened database files, if any + final ArraySet<String> concurrent; + synchronized (sActiveDatabases) { + concurrent = sOpenTracker.getConcurrentDatabasePaths(); + } + if (concurrent.size() > 0) { + printer.println(""); + printer.println("Concurrently opened database files"); + for (String f : concurrent) { + printer.println(" " + f); + } + } } private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) { diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 39b6aeb814f6..8c7050176506 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -86,3 +86,11 @@ flag { description: "This flag is used to enabled the Wallet Role for all users on the device" bug: "283989236" } + +flag { + name: "signature_permission_allowlist_enabled" + is_fixed_read_only: true + namespace: "permissions" + description: "Enable signature permission allowlist" + bug: "308573169" +} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e0bda9181380..3c36227eda0a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -26,7 +26,6 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; -import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -1012,10 +1011,8 @@ public final class ViewRootImpl implements ViewParent, // Used to check if there were any view invalidations in // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). private boolean mHasInvalidation = false; - // Used to check if it is in the frame rate boosting period. + // Used to check if it is in the touch boosting period. private boolean mIsFrameRateBoosting = false; - // Used to check if it is in touch boosting period. - private boolean mIsTouchBoosting = false; // Used to check if there is a message in the message queue // for idleness handling. private boolean mHasIdledMessage = false; @@ -6424,12 +6421,11 @@ public final class ViewRootImpl implements ViewParent, * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). */ mIsFrameRateBoosting = false; - mIsTouchBoosting = false; setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, mLastPreferredFrameRateCategory)); break; case MSG_CHECK_INVALIDATION_IDLE: - if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) { + if (!mHasInvalidation && !mIsFrameRateBoosting) { mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; setPreferredFrameRateCategory(mPreferredFrameRateCategory); mHasIdledMessage = false; @@ -7452,7 +7448,7 @@ public final class ViewRootImpl implements ViewParent, // For the variable refresh rate project if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { // set the frame rate to the maximum value. - mIsTouchBoosting = true; + mIsFrameRateBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); } /** @@ -12204,16 +12200,8 @@ public final class ViewRootImpl implements ViewParent, return; } - int frameRateCategory = mIsTouchBoosting - ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory; - - // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT - // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction. - // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction - // (e.g., Window Initialization). - if (mIsFrameRateBoosting || mInsetsAnimationRunning) { - frameRateCategory = FRAME_RATE_CATEGORY_HIGH; - } + int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning + ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; try { if (mLastPreferredFrameRateCategory != frameRateCategory) { diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index e118c98dd4da..3ee565f8e025 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -403,4 +403,41 @@ public class SQLiteDatabaseTest { } assertFalse(allowed); } + + /** Return true if the path is in the list of strings. */ + private boolean isConcurrent(String path) throws Exception { + path = new File(path).toPath().toRealPath().toString(); + return SQLiteDatabase.getConcurrentDatabasePaths().contains(path); + } + + @Test + public void testDuplicateDatabases() throws Exception { + // The two database paths in this test are assumed not to have been opened earlier in this + // process. + + // A database path that will be opened twice. + final String dbName = "never-used-db.db"; + final File dbFile = mContext.getDatabasePath(dbName); + final String dbPath = dbFile.getPath(); + + // A database path that will be opened only once. + final String okName = "never-used-ok.db"; + final File okFile = mContext.getDatabasePath(okName); + final String okPath = okFile.getPath(); + + SQLiteDatabase db1 = SQLiteDatabase.openOrCreateDatabase(dbFile, null); + assertFalse(isConcurrent(dbPath)); + SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(dbFile, null); + assertTrue(isConcurrent(dbPath)); + db1.close(); + assertTrue(isConcurrent(dbPath)); + db2.close(); + assertTrue(isConcurrent(dbPath)); + + SQLiteDatabase db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null); + db3.close(); + db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null); + assertFalse(isConcurrent(okPath)); + db3.close(); + } } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index cfbda84f24e1..cf3eb12498ca 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -19,7 +19,6 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; -import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; @@ -576,13 +575,8 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_HIGH_HINT); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java index 30d5edb59c85..160f922dd928 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java @@ -199,6 +199,10 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { } private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) { + if (leash == null || !leash.isValid()) { + return; + } + final float scale = targetRect.width() / mStartTaskRect.width(); mTransformMatrix.reset(); mTransformMatrix.setScale(scale, scale); @@ -211,12 +215,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { private void finishAnimation() { if (mEnteringTarget != null) { - mTransaction.setCornerRadius(mEnteringTarget.leash, 0); - mEnteringTarget.leash.release(); + if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) { + mTransaction.setCornerRadius(mEnteringTarget.leash, 0); + mEnteringTarget.leash.release(); + } mEnteringTarget = null; } if (mClosingTarget != null) { - mClosingTarget.leash.release(); + if (mClosingTarget.leash != null) { + mClosingTarget.leash.release(); + } mClosingTarget = null; } if (mBackground != null) { @@ -260,7 +268,9 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { } private void onGestureCommitted() { - if (mEnteringTarget == null || mClosingTarget == null) { + if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null + || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid() + || !mClosingTarget.leash.isValid()) { finishAnimation(); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index ac2a1c867462..adc78391f033 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -208,7 +208,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { float top = mapRange(progress, mClosingStartRect.top, targetTop); float width = mapRange(progress, mClosingStartRect.width(), targetWidth); float height = mapRange(progress, mClosingStartRect.height(), targetHeight); - mTransaction.setLayer(mClosingTarget.leash, 0); + if (mClosingTarget.leash != null && mClosingTarget.leash.isValid()) { + mTransaction.setLayer(mClosingTarget.leash, 0); + } mClosingCurrentRect.set(left, top, left + width, top + height); applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius); @@ -226,7 +228,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { /** Transform the target window to match the target rect. */ private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) { - if (leash == null) { + if (leash == null || !leash.isValid()) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index d4a92894a397..a5f7880cfd41 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -137,7 +137,7 @@ import java.util.function.IntConsumer; * The controller manages addition, removal, and visible state of bubbles on screen. */ public class BubbleController implements ConfigurationChangeListener, - RemoteCallable<BubbleController> { + RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; @@ -706,6 +706,7 @@ public class BubbleController implements ConfigurationChangeListener, return mBubbleIconFactory; } + @Override public Bubbles.SysuiProxy getSysuiProxy() { return mSysuiProxy; } @@ -732,8 +733,7 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView == null) { mStackView = new BubbleStackView( mContext, this, mBubbleData, mSurfaceSynchronizer, - mFloatingContentCoordinator, - mMainExecutor); + mFloatingContentCoordinator, this, mMainExecutor); mStackView.onOrientationChanged(); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index c25d41275f2b..a619401301aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -206,6 +206,7 @@ public class BubbleStackView extends FrameLayout }; private final BubbleController mBubbleController; private final BubbleData mBubbleData; + private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider; private StackViewState mStackViewState = new StackViewState(); private final ValueAnimator mDismissBubbleAnimator; @@ -875,12 +876,14 @@ public class BubbleStackView extends FrameLayout public BubbleStackView(Context context, BubbleController bubbleController, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, + Bubbles.SysuiProxy.Provider sysuiProxyProvider, ShellExecutor mainExecutor) { super(context); mMainExecutor = mainExecutor; mBubbleController = bubbleController; mBubbleData = data; + mSysuiProxyProvider = sysuiProxyProvider; Resources res = getResources(); mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); @@ -2090,7 +2093,7 @@ public class BubbleStackView extends FrameLayout hideCurrentInputMethod(); - mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand); + mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand); if (wasExpanded) { stopMonitoringSwipeUpGesture(); @@ -3034,7 +3037,7 @@ public class BubbleStackView extends FrameLayout if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { mManageMenu.setVisibility(View.INVISIBLE); mManageMenuScrim.setVisibility(INVISIBLE); - mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */); + mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */); return; } if (show) { @@ -3048,7 +3051,7 @@ public class BubbleStackView extends FrameLayout } }; - mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show); + mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(show); mManageMenuScrim.animate() .setInterpolator(show ? ALPHA_IN : ALPHA_OUT) .alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 759246eb285d..28af0ca6ac6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -321,6 +321,13 @@ public interface Bubbles { /** Callback to tell SysUi components execute some methods. */ interface SysuiProxy { + + /** Provider interface for {@link SysuiProxy}. */ + interface Provider { + /** Returns {@link SysuiProxy}. */ + SysuiProxy getSysuiProxy(); + } + void isNotificationPanelExpand(Consumer<Boolean> callback); void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index f58332198696..4878df806f82 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -125,7 +125,7 @@ class BubbleViewInfoTest : ShellTestCase() { mock<BubbleProperties>()) bubbleStackView = BubbleStackView(context, bubbleController, bubbleData, - surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor) + surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor) bubbleBarLayerView = BubbleBarLayerView(context, bubbleController) } diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp index 4d020c567972..5f5ffe97e953 100644 --- a/libs/hwui/AutoBackendTextureRelease.cpp +++ b/libs/hwui/AutoBackendTextureRelease.cpp @@ -21,6 +21,7 @@ #include <include/gpu/GrDirectContext.h> #include <include/gpu/GrBackendSurface.h> #include <include/gpu/MutableTextureState.h> +#include <include/gpu/vk/VulkanMutableTextureState.h> #include "renderthread/RenderThread.h" #include "utils/Color.h" #include "utils/PaintUtils.h" @@ -142,8 +143,9 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context) LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan); if (mBackendTexture.isValid()) { // Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout. - skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED, - VK_QUEUE_FAMILY_FOREIGN_EXT); + skgpu::MutableTextureState newState = skgpu::MutableTextureStates::MakeVulkan( + VK_IMAGE_LAYOUT_UNDEFINED, + VK_QUEUE_FAMILY_FOREIGN_EXT); // The unref for this ref happens in the releaseProc passed into setBackendTextureState. The // releaseProc callback will be made when the work to set the new state has finished on the diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp index acf893e9544c..79acb6cc35e5 100644 --- a/libs/hwui/jni/PathMeasure.cpp +++ b/libs/hwui/jni/PathMeasure.cpp @@ -17,7 +17,11 @@ #include "GraphicsJNI.h" +#include "SkMatrix.h" +#include "SkPath.h" #include "SkPathMeasure.h" +#include "SkPoint.h" +#include "SkScalar.h" /* We declare an explicit pair, so that we don't have to rely on the java client to be sure not to edit the path while we have an active measure diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 17f25255fd4b..687feef6c58a 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -3109,9 +3109,8 @@ public final class MediaRouter2 { mStub, mDiscoveryPreference); } - if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { - unregisterRouterStubLocked(); - } + unregisterRouterStubIfNeededLocked(); + } catch (RemoteException ex) { Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); } @@ -3319,13 +3318,12 @@ public final class MediaRouter2 { obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller)); } - if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { - try { - unregisterRouterStubLocked(); - } catch (RemoteException ex) { - ex.rethrowFromSystemServer(); - } + try { + unregisterRouterStubIfNeededLocked(); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); } + } } @@ -3339,8 +3337,10 @@ public final class MediaRouter2 { } @GuardedBy("mLock") - private void unregisterRouterStubLocked() throws RemoteException { - if (mStub != null) { + private void unregisterRouterStubIfNeededLocked() throws RemoteException { + if (mStub != null + && mRouteCallbackRecords.isEmpty() + && mNonSystemRoutingControllers.isEmpty()) { mMediaRouterService.unregisterRouter2(mStub); mStub = null; } diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java index 74b6fc13ade4..71147f4becb5 100644 --- a/media/java/android/media/audiofx/Virtualizer.java +++ b/media/java/android/media/audiofx/Virtualizer.java @@ -46,6 +46,11 @@ import java.util.StringTokenizer; * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions. * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling * audio effects. + * + * @deprecated use the {@link android.media.Spatializer} class to query the capabilities of the + * platform with regards to spatialization, a different name for audio channel virtualization, + * and the {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)} to + * characterize how you want your content to be played when spatialization is supported. */ public class Virtualizer extends AudioEffect { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index d6f1eab442fa..15f33d2cff42 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -229,6 +229,17 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getId() { + if (com.android.media.flags.Flags.enableAudioPoliciesDeviceAndBluetoothController()) { + // Note: be careful when removing this flag. Instead of just removing it, you might want + // to replace it with SDK_INT >= 35. Explanation: The presence of SDK checks in settings + // lib suggests that a mainline component may depend on this code. Which means removing + // this "if" (and using always the route info id) could mean a regression on mainline + // code running on a device that's running API 34 or older. Unfortunately, we cannot + // check the API level at the moment of writing this code because the API level has not + // been bumped, yet. + return mRouteInfo.getId(); + } + String id; switch (mRouteInfo.getType()) { case TYPE_WIRED_HEADSET: diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 1746befbfa4d..ceba9be70487 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -31,10 +31,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.media.MediaRoute2Info; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import com.android.media.flags.Flags; import com.android.settingslib.R; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -45,6 +50,8 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class PhoneMediaDeviceTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private MediaRoute2Info mInfo; @@ -110,8 +117,18 @@ public class PhoneMediaDeviceTest { .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name)); } + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + @Test + public void getId_whenAdvancedWiredRoutingEnabled_returnCorrectId() { + String fakeId = "foo"; + when(mInfo.getId()).thenReturn(fakeId); + + assertThat(mPhoneMediaDevice.getId()).isEqualTo(fakeId); + } + + @DisableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) @Test - public void getId_returnCorrectId() { + public void getId_whenAdvancedWiredRoutingDisabled_returnCorrectId() { when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES); assertThat(mPhoneMediaDevice.getId()) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 2d442f4c0e6e..3a46f4e96ccb 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -406,7 +406,7 @@ public class SettingsProvider extends ContentProvider { Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); - mSettingsRegistry = new SettingsRegistry(); + mSettingsRegistry = new SettingsRegistry(mHandlerThread.getLooper()); } SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext()); synchronized (mLock) { @@ -2896,8 +2896,8 @@ public class SettingsProvider extends ContentProvider { private String mSettingsCreationBuildId; - public SettingsRegistry() { - mHandler = new MyHandler(getContext().getMainLooper()); + SettingsRegistry(Looper looper) { + mHandler = new MyHandler(looper); mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers()); mBackupManager = new BackupManager(getContext()); } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 323613077a70..2c35c777ab12 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -89,6 +89,13 @@ flag { } flag { + name: "notification_avalanche_suppression" + namespace: "systemui" + description: "After notification avalanche floodgate event, suppress HUNs completely." + bug: "321089634" +} + +flag { name: "notification_background_tint_optimization" namespace: "systemui" description: "Re-enable the codepath that removed tinting of notifications when the" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 867a48dc3925..a2dec5ff8830 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -24,19 +24,21 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository +import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository +import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -59,8 +61,6 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var communalRepository: FakeCommunalRepository private lateinit var tutorialRepository: FakeCommunalTutorialRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository @@ -72,17 +72,14 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - val withDeps = CommunalInteractorFactory.create(testScope) - keyguardRepository = withDeps.keyguardRepository - communalRepository = withDeps.communalRepository - tutorialRepository = withDeps.tutorialRepository - widgetRepository = withDeps.widgetRepository - smartspaceRepository = withDeps.smartspaceRepository - mediaRepository = withDeps.mediaRepository + tutorialRepository = kosmos.fakeCommunalTutorialRepository + widgetRepository = kosmos.fakeCommunalWidgetRepository + smartspaceRepository = kosmos.fakeSmartspaceRepository + mediaRepository = kosmos.fakeCommunalMediaRepository underTest = CommunalEditModeViewModel( - withDeps.communalInteractor, + kosmos.communalInteractor, mediaHost, uiEventLogger, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 9ac21dd69aab..033dc6d1dbcb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -23,25 +23,30 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository +import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository +import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before @@ -58,15 +63,14 @@ import org.mockito.MockitoAnnotations class CommunalViewModelTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost - private lateinit var testScope: TestScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var communalRepository: FakeCommunalRepository private lateinit var tutorialRepository: FakeCommunalTutorialRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository - private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var underTest: CommunalViewModel @@ -74,22 +78,17 @@ class CommunalViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() - - val withDeps = CommunalInteractorFactory.create() - keyguardRepository = withDeps.keyguardRepository - communalRepository = withDeps.communalRepository - tutorialRepository = withDeps.tutorialRepository - widgetRepository = withDeps.widgetRepository - smartspaceRepository = withDeps.smartspaceRepository - mediaRepository = withDeps.mediaRepository - communalPrefsRepository = withDeps.communalPrefsRepository + keyguardRepository = kosmos.fakeKeyguardRepository + tutorialRepository = kosmos.fakeCommunalTutorialRepository + widgetRepository = kosmos.fakeCommunalWidgetRepository + smartspaceRepository = kosmos.fakeSmartspaceRepository + mediaRepository = kosmos.fakeCommunalMediaRepository underTest = CommunalViewModel( testScope, - withDeps.communalInteractor, - withDeps.tutorialInteractor, + kosmos.communalInteractor, + kosmos.communalTutorialInteractor, mediaHost, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 6f62afc560fe..dc8b97abbfe8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -139,6 +139,18 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun topClippingBounds() = + testScope.runTest { + assertThat(underTest.topClippingBounds.value).isNull() + + underTest.topClippingBounds.value = 50 + assertThat(underTest.topClippingBounds.value).isEqualTo(50) + + underTest.topClippingBounds.value = 500 + assertThat(underTest.topClippingBounds.value).isEqualTo(500) + } + + @Test fun clockPosition() = testScope.runTest { assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 1eaa0602b258..cceb76725615 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState @@ -54,13 +55,12 @@ class KeyguardRootViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository private val screenOffAnimationController = kosmos.screenOffAnimationController private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor private val dozeParameters = kosmos.dozeParameters - private val underTest by lazy { - kosmos.keyguardRootViewModel - } + private val underTest by lazy { kosmos.keyguardRootViewModel } @Before fun setUp() { @@ -207,6 +207,19 @@ class KeyguardRootViewModelTest : SysuiTestCase() { } @Test + fun topClippingBounds() = + testScope.runTest { + val topClippingBounds by collectLastValue(underTest.topClippingBounds) + assertThat(topClippingBounds).isNull() + + keyguardRepository.topClippingBounds.value = 50 + assertThat(topClippingBounds).isEqualTo(50) + + keyguardRepository.topClippingBounds.value = 1000 + assertThat(topClippingBounds).isEqualTo(1000) + } + + @Test fun alpha_glanceableHubOpen_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha) diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml index 3be99939ba0f..156c98333f3a 100644 --- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml +++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml @@ -37,6 +37,7 @@ android:layout_height="wrap_content" android:background="@drawable/qs_customizer_toolbar" android:navigationContentDescription="@*android:string/action_bar_up_description" + android:titleTextAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title" style="@style/QSCustomizeToolbar" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 033f93b260ab..ad303171d22e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -477,9 +477,13 @@ public class KeyguardClockSwitch extends RelativeLayout { public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardClockSwitch:"); pw.println(" mSmallClockFrame = " + mSmallClockFrame); - pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha()); + if (mSmallClockFrame != null) { + pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha()); + } pw.println(" mLargeClockFrame = " + mLargeClockFrame); - pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha()); + if (mLargeClockFrame != null) { + pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha()); + } pw.println(" mStatusArea = " + mStatusArea); pw.println(" mDisplayedClockSize = " + mDisplayedClockSize); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 2bb9d0e52757..a909383f4a2c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -74,6 +74,7 @@ constructor( // before the MediaHierarchyManager attempts to move the UMO to the hub. with(mediaHost) { expansion = MediaHostState.EXPANDED + expandedMatchesParentHeight = true showsOnlyActiveMedia = false falsingProtectionNeeded = false init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index d012d24b767e..14371949c9c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -132,6 +132,9 @@ interface KeyguardRepository { */ val isDozing: StateFlow<Boolean> + /** Keyguard can be clipped at the top as the shade is dragged */ + val topClippingBounds: MutableStateFlow<Int?> + /** * Observable for whether the device is dreaming. * @@ -326,6 +329,8 @@ constructor( private val _clockShouldBeCentered = MutableStateFlow(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow() + override val topClippingBounds = MutableStateFlow<Int?>(null) + override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 6170356d4a90..91747e0f69a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -171,6 +171,14 @@ constructor( /** Whether the keyguard is going away. */ val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway + /** Keyguard can be clipped at the top as the shade is dragged */ + val topClippingBounds: Flow<Int?> = + combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) { + _, + topClippingBounds -> + topClippingBounds + } + /** Last point that [KeyguardRootView] view was tapped */ val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow() @@ -328,6 +336,10 @@ constructor( repository.keyguardDoneAnimationsFinished() } + fun setTopClippingBounds(top: Int?) { + repository.topClippingBounds.value = top + } + companion object { private const val TAG = "KeyguardInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 2aebd99e3664..48092c6374e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.annotation.DrawableRes import android.annotation.SuppressLint import android.graphics.Point +import android.graphics.Rect import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener @@ -158,6 +159,23 @@ object KeyguardRootViewBinder { } launch { + val clipBounds = Rect() + viewModel.topClippingBounds.collect { clipTop -> + if (clipTop == null) { + view.setClipBounds(null) + } else { + clipBounds.apply { + top = clipTop + left = view.getLeft() + right = view.getRight() + bottom = view.getBottom() + } + view.setClipBounds(clipBounds) + } + } + } + + launch { viewModel.lockscreenStateAlpha.collect { alpha -> childViews[statusViewId]?.alpha = alpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 5d36da9b9df1..ea6670267270 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -80,6 +80,12 @@ constructor( val notificationBounds: StateFlow<NotificationContainerBounds> = keyguardInteractor.notificationContainerBounds + /** + * The keyguard root view can be clipped as the shade is pulled down, typically only for + * non-split shade cases. + */ + val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds + /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = merge( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt index 631a0b8471b8..437218f9f440 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt @@ -228,6 +228,14 @@ constructor( } } + override var expandedMatchesParentHeight: Boolean = false + set(value) { + if (value != field) { + field = value + changedListener?.invoke() + } + } + override var squishFraction: Float = 1.0f set(value) { if (!value.equals(field)) { @@ -282,6 +290,7 @@ constructor( override fun copy(): MediaHostState { val mediaHostState = MediaHostStateHolder() mediaHostState.expansion = expansion + mediaHostState.expandedMatchesParentHeight = expandedMatchesParentHeight mediaHostState.squishFraction = squishFraction mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() @@ -360,6 +369,12 @@ interface MediaHostState { */ var expansion: Float + /** + * If true, the [EXPANDED] layout should stretch to match the height of its parent container, + * rather than having a fixed height. + */ + var expandedMatchesParentHeight: Boolean + /** Fraction of the height animation. */ var squishFraction: Float diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index be9393655c5d..962764c028fc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.res.Configuration import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT import com.android.app.tracing.traceSection import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaViewHolder @@ -152,18 +153,11 @@ constructor( lastOrientation = newOrientation // Update the height of media controls for the expanded layout. it is needed // for large screen devices. - val backgroundIds = - if (type == TYPE.PLAYER) { - MediaViewHolder.backgroundIds - } else { - setOf(RecommendationViewHolder.backgroundId) - } - backgroundIds.forEach { id -> - expandedLayout.getConstraint(id).layout.mHeight = - context.resources.getDimensionPixelSize( - R.dimen.qs_media_session_height_expanded - ) - } + setBackgroundHeights( + context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_height_expanded + ) + ) } if (this@MediaViewController::configurationChangeListener.isInitialized) { configurationChangeListener.invoke() @@ -276,6 +270,17 @@ constructor( private fun constraintSetForExpansion(expansion: Float): ConstraintSet = if (expansion > 0) expandedLayout else collapsedLayout + /** Set the height of UMO background constraints. */ + private fun setBackgroundHeights(height: Int) { + val backgroundIds = + if (type == TYPE.PLAYER) { + MediaViewHolder.backgroundIds + } else { + setOf(RecommendationViewHolder.backgroundId) + } + backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height } + } + /** * Set the views to be showing/hidden based on the [isGutsVisible] for a given * [TransitionViewState]. @@ -454,6 +459,18 @@ constructor( } // Let's create a new measurement if (state.expansion == 0.0f || state.expansion == 1.0f) { + if (state.expansion == 1.0f) { + val height = + if (state.expandedMatchesParentHeight) { + MATCH_CONSTRAINT + } else { + context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_height_expanded + ) + } + setBackgroundHeights(height) + } + result = transitionLayout!!.calculateViewState( state.measurementInput!!, diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index a103566400a6..07705f361881 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -58,6 +58,7 @@ public class QSCustomizer extends LinearLayout { private final RecyclerView mRecyclerView; private boolean mCustomizing; private QSContainerController mQsContainerController; + private final Toolbar mToolbar; private QS mQs; private int mX; private int mY; @@ -69,15 +70,15 @@ public class QSCustomizer extends LinearLayout { LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this); mClipper = new QSDetailClipper(findViewById(R.id.customize_container)); - Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar); + mToolbar = findViewById(com.android.internal.R.id.action_bar); TypedValue value = new TypedValue(); mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); - toolbar.setNavigationIcon( + mToolbar.setNavigationIcon( getResources().getDrawable(value.resourceId, mContext.getTheme())); - toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) + mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - toolbar.setTitle(R.string.qs_edit); + mToolbar.setTitle(R.string.qs_edit); mRecyclerView = findViewById(android.R.id.list); mTransparentView = findViewById(R.id.customizer_transparent_view); DefaultItemAnimator animator = new DefaultItemAnimator(); @@ -184,6 +185,14 @@ public class QSCustomizer extends LinearLayout { return isShown; } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mToolbar.setTitleTextAppearance(mContext, + android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title); + updateToolbarMenuFontSize(); + } + void setCustomizing(boolean customizing) { mCustomizing = customizing; if (mQs != null) { @@ -269,4 +278,11 @@ public class QSCustomizer extends LinearLayout { lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext); mTransparentView.setLayoutParams(lp); } + + private void updateToolbarMenuFontSize() { + // Clearing and re-adding the toolbar action force updates the font size + mToolbar.getMenu().clear(); + mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 456520051f58..c5eeb2f87d3a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -249,6 +249,10 @@ open class QSTileViewImpl @JvmOverloads constructor( height = iconSize marginEnd = endMargin } + + background = createTileBackground() + setColor(backgroundColor) + setOverlayColor(backgroundOverlayColor) } private fun createAndAddLabels() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 46806e66cb8c..54b6ad71e734 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -790,7 +790,7 @@ private fun <R> HeadsUpManager.modifyHuns(block: (HunMutator) -> R): R { /** Mutates the HeadsUp state of notifications. */ private interface HunMutator { - fun updateNotification(key: String, alert: Boolean) + fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) fun removeNotification(key: String, releaseImmediately: Boolean) } @@ -801,8 +801,8 @@ private interface HunMutator { private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator { private val deferred = mutableListOf<Pair<String, Boolean>>() - override fun updateNotification(key: String, alert: Boolean) { - headsUpManager.updateNotification(key, alert) + override fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) { + headsUpManager.updateNotification(key, shouldHeadsUpAgain) } override fun removeNotification(key: String, releaseImmediately: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt index 380cdadd1361..ae4ba27775b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.os.UserHandle import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags.screenshareNotificationHiding import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import dagger.Binds import dagger.Module @@ -55,6 +57,8 @@ class SensitiveContentCoordinatorImpl @Inject constructor( private val statusBarStateController: StatusBarStateController, private val keyguardStateController: KeyguardStateController, private val selectedUserInteractor: SelectedUserInteractor, + private val sensitiveNotificationProtectionController: + SensitiveNotificationProtectionController, ) : Invalidator("SensitiveContentInvalidator"), SensitiveContentCoordinator, DynamicPrivacyController.Listener, @@ -82,10 +86,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor( return } + val isSensitiveContentProtectionActive = screenshareNotificationHiding() && + sensitiveNotificationProtectionController.isSensitiveStateActive val currentUserId = lockscreenUserManager.currentUserId val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId) - val deviceSensitive = devicePublic && - !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId) + val deviceSensitive = (devicePublic && + !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) || + isSensitiveContentProtectionActive val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) { val notifUserId = entry.sbn.user.identifier @@ -105,9 +112,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor( else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId) } } + + val shouldProtectNotification = screenshareNotificationHiding() && + sensitiveNotificationProtectionController.shouldProtectNotification(entry) + val needsRedaction = lockscreenUserManager.needsRedaction(entry) val isSensitive = userPublic && needsRedaction - entry.setSensitive(isSensitive, deviceSensitive) + entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 0bc8e682c891..f375ebce2de3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -187,7 +187,7 @@ object NotificationIconContainerViewBinder { configuration.getDimensionPixelSize(RInternal.dimen.status_bar_icon_size_sp) val iconHorizontalPaddingFlow: Flow<Int> = configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin) - val layoutParams: Flow<FrameLayout.LayoutParams> = + val layoutParams: StateFlow<FrameLayout.LayoutParams> = combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) { iconSize, iconHPadding, @@ -206,7 +206,7 @@ object NotificationIconContainerViewBinder { private suspend fun Flow<NotificationIconsViewData>.bindIcons( view: NotificationIconContainer, - layoutParams: Flow<FrameLayout.LayoutParams>, + layoutParams: StateFlow<FrameLayout.LayoutParams>, notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit, @@ -259,7 +259,7 @@ object NotificationIconContainerViewBinder { // added again. removeTransientView(sbiv) } - view.addView(sbiv) + view.addView(sbiv, layoutParams.value) boundViewsByNotifKey.remove(notifKey)?.second?.cancel() boundViewsByNotifKey[notifKey] = Pair( @@ -267,7 +267,9 @@ object NotificationIconContainerViewBinder { launch { launch { layoutParams.collectTracingEach("SBIV#bindLayoutParams") { - sbiv.layoutParams = it + if (it != sbiv.layoutParams) { + sbiv.layoutParams = it + } } } bindIcon(notifKey, sbiv) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt new file mode 100644 index 000000000000..a21dd9bb9579 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt @@ -0,0 +1,53 @@ +/* + * 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.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the notification avalanche suppression flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationAvalancheSuppression { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationAvalancheSuppression() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 6a66bb74f16d..9a8cc0ae33cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -22,6 +22,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N import static com.android.app.animation.Interpolators.STANDARD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; +import static com.android.systemui.Flags.screenshareNotificationHiding; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener; @@ -135,6 +136,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; @@ -218,6 +220,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final SecureSettings mSecureSettings; private final NotificationDismissibilityProvider mDismissibilityProvider; private final ActivityStarter mActivityStarter; + private final SensitiveNotificationProtectionController + mSensitiveNotificationProtectionController; private View mLongPressedView; @@ -295,6 +299,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; + private final Runnable mSensitiveStateChangedListener = new Runnable() { + @Override + public void run() { + // Animate false to protect against screen recording capturing content + // during the animation + updateSensitivenessWithAnimation(false); + } + }; + private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { if (mView.isExpanded()) { // The bottom might change because we're using the final actual height of the view @@ -399,7 +412,20 @@ public class NotificationStackScrollLayoutController implements Dumpable { } private void updateSensitivenessWithAnimation(boolean animate) { - mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode()); + Trace.beginSection("NSSLC.updateSensitivenessWithAnimation"); + if (screenshareNotificationHiding()) { + boolean isAnyProfilePublic = mLockscreenUserManager.isAnyProfilePublicMode(); + boolean isSensitiveContentProtectionActive = + mSensitiveNotificationProtectionController.isSensitiveStateActive(); + boolean isSensitive = isAnyProfilePublic || isSensitiveContentProtectionActive; + + // Only animate if in a non-sensitive state (not screen sharing) + boolean shouldAnimate = animate && !isSensitiveContentProtectionActive; + mView.updateSensitiveness(shouldAnimate, isSensitive); + } else { + mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode()); + } + Trace.endSection(); } /** @@ -708,7 +734,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { SecureSettings secureSettings, NotificationDismissibilityProvider dismissibilityProvider, ActivityStarter activityStarter, - SplitShadeStateController splitShadeStateController) { + SplitShadeStateController splitShadeStateController, + SensitiveNotificationProtectionController sensitiveNotificationProtectionController) { mView = view; mKeyguardTransitionRepo = keyguardTransitionRepo; mViewBinder = viewBinder; @@ -756,6 +783,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mSecureSettings = secureSettings; mDismissibilityProvider = dismissibilityProvider; mActivityStarter = activityStarter; + mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController; mView.passSplitShadeStateController(splitShadeStateController); mDumpManager.registerDumpable(this); updateResources(); @@ -860,6 +888,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); mDeviceProvisionedListener.onDeviceProvisionedChanged(); + if (screenshareNotificationHiding()) { + mSensitiveNotificationProtectionController + .registerSensitiveStateListener(mSensitiveStateChangedListener); + } + if (mView.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index ae04eaf49b65..459b368b5ac9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -60,6 +60,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -217,6 +218,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private final ScreenOffAnimationController mScreenOffAnimationController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final KeyguardInteractor mKeyguardInteractor; private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; @@ -311,6 +313,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel, KeyguardTransitionInteractor keyguardTransitionInteractor, + KeyguardInteractor keyguardInteractor, WallpaperRepository wallpaperRepository, @Main CoroutineDispatcher mainDispatcher, LargeScreenShadeInterpolator largeScreenShadeInterpolator) { @@ -357,6 +360,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; + mKeyguardInteractor = keyguardInteractor; mWallpaperRepository = wallpaperRepository; mMainDispatcher = mainDispatcher; } @@ -759,7 +763,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump // see: b/186644628 mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom); mScrimBehind.setBottomEdgePosition((int) top); + mKeyguardInteractor.setTopClippingBounds((int) top); } else { + mKeyguardInteractor.setTopClippingBounds(null); mNotificationsScrim.setDrawableBounds(left, top, right, bottom); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 1528c9befda7..1414150c5511 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -159,7 +159,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { public void showNotification(@NonNull NotificationEntry entry) { mLogger.logShowNotification(entry); addEntry(entry); - updateNotification(entry.getKey(), true /* show */); + updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */); entry.setInterruption(); } @@ -190,12 +190,12 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { /** * Called when the notification state has been updated. * @param key the key of the entry that was updated - * @param show whether the notification should show again and force reevaluation of - * removal time + * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation + * of removal time */ - public void updateNotification(@NonNull String key, boolean show) { + public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); - mLogger.logUpdateNotification(key, show, headsUpEntry != null); + mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null); if (headsUpEntry == null) { // the entry was released before this update (i.e by a listener) This can happen // with the groupmanager @@ -204,7 +204,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - if (show) { + if (shouldHeadsUpAgain) { headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification"); if (headsUpEntry != null) { setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt index b8c7e202ce7c..a7352be8d80a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt @@ -182,7 +182,7 @@ interface HeadsUpManager : Dumpable { */ fun unpinAll(userUnPinned: Boolean) - fun updateNotification(key: String, alert: Boolean) + fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) } /** Sets the animation state of the HeadsUpManager. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java new file mode 100644 index 000000000000..970cc75bbb6b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java @@ -0,0 +1,41 @@ +/* + * 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.policy; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * A controller which provides the current sensitive notification protections status as well as + * to assist in feature usage and exemptions + */ +public interface SensitiveNotificationProtectionController { + /** + * Register a runnable that triggers on changes to protection state + * + * <p> onSensitiveStateChanged not invoked on registration + */ + void registerSensitiveStateListener(Runnable onSensitiveStateChanged); + + /** Unregister a previously registered onSensitiveStateChanged runnable */ + void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged); + + /** Return {@code true} if device in state in which notifications should be protected */ + boolean isSensitiveStateActive(); + + /** Return {@code true} when notification should be protected */ + boolean shouldProtectNotification(NotificationEntry entry); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java new file mode 100644 index 000000000000..3c4ca4465874 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java @@ -0,0 +1,104 @@ +/* + * 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.policy; + +import static com.android.systemui.Flags.screenshareNotificationHiding; + +import android.media.projection.MediaProjectionInfo; +import android.media.projection.MediaProjectionManager; +import android.os.Handler; +import android.os.Trace; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.util.ListenerSet; + +import javax.inject.Inject; + +/** Implementation of SensitiveNotificationProtectionController. **/ +@SysUISingleton +public class SensitiveNotificationProtectionControllerImpl + implements SensitiveNotificationProtectionController { + private final MediaProjectionManager mMediaProjectionManager; + private final ListenerSet<Runnable> mListeners = new ListenerSet<>(); + private volatile MediaProjectionInfo mProjection; + + @VisibleForTesting + final MediaProjectionManager.Callback mMediaProjectionCallback = + new MediaProjectionManager.Callback() { + @Override + public void onStart(MediaProjectionInfo info) { + Trace.beginSection( + "SNPC.onProjectionStart"); + mProjection = info; + mListeners.forEach(Runnable::run); + Trace.endSection(); + } + + @Override + public void onStop(MediaProjectionInfo info) { + Trace.beginSection( + "SNPC.onProjectionStop"); + mProjection = null; + mListeners.forEach(Runnable::run); + Trace.endSection(); + } + }; + + @Inject + public SensitiveNotificationProtectionControllerImpl( + MediaProjectionManager mediaProjectionManager, + @Main Handler mainHandler) { + mMediaProjectionManager = mediaProjectionManager; + + if (screenshareNotificationHiding()) { + mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler); + } + } + + @Override + public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) { + mListeners.addIfAbsent(onSensitiveStateChanged); + } + + @Override + public void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged) { + mListeners.remove(onSensitiveStateChanged); + } + + @Override + public boolean isSensitiveStateActive() { + // TODO(b/316955558): Add disabled by developer option + // TODO(b/316955306): Add feature exemption for sysui and bug handlers + // TODO(b/316955346): Add feature exemption for single app screen sharing + return mProjection != null; + } + + @Override + public boolean shouldProtectNotification(NotificationEntry entry) { + if (!isSensitiveStateActive()) { + return false; + } + + // Exempt foreground service notifications from protection in effort to keep screen share + // stop actions easily accessible + // TODO(b/316955208): Exempt FGS notifications only for app that started projection + return !entry.getSbn().getNotification().isFgsOrUij(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 3304b9827fd8..15200bd0ac54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -60,6 +60,8 @@ import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockControllerImpl; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityControllerImpl; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionControllerImpl; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; @@ -146,6 +148,11 @@ public interface StatusBarPolicyModule { /** */ @Binds + SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController( + SensitiveNotificationProtectionControllerImpl controllerImpl); + + /** */ + @Binds UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt index ce811e205436..10137a0d3430 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt @@ -17,10 +17,16 @@ package com.android.systemui.statusbar.ui import com.android.internal.policy.SystemBarUtils +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -31,6 +37,8 @@ import kotlinx.coroutines.flow.onStart class SystemBarUtilsState @Inject constructor( + @Background bgContext: CoroutineContext, + @Main mainContext: CoroutineContext, configurationController: ConfigurationController, proxy: SystemBarUtilsProxy, ) { @@ -38,5 +46,10 @@ constructor( val statusBarHeight: Flow<Int> = configurationController.onConfigChanged .onStart<Any> { emit(Unit) } + .flowOn(mainContext) + .conflate() .map { proxy.getStatusBarHeight() } + .distinctUntilChanged() + .flowOn(bgContext) + .conflate() } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt index b8f95832b852..1ba269e25dff 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt @@ -24,7 +24,7 @@ import android.content.IntentFilter import android.os.UserHandle import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository @@ -54,7 +54,7 @@ interface WallpaperRepository { class WallpaperRepositoryImpl @Inject constructor( - @Application scope: CoroutineScope, + @Background scope: CoroutineScope, broadcastDispatcher: BroadcastDispatcher, userRepository: UserRepository, private val wallpaperManager: WallpaperManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 90eaa5a2eeaf..dafd9e64371b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -23,8 +23,9 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.flags.FakeFeatureFlags @@ -34,6 +35,8 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel @@ -42,11 +45,15 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -57,7 +64,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent @@ -83,7 +89,8 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(JUnit4::class) class KeyguardTransitionScenariosTest : SysuiTestCase() { - private lateinit var testScope: TestScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var bouncerRepository: FakeKeyguardBouncerRepository @@ -119,17 +126,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - val testDispatcher = StandardTestDispatcher() - testScope = TestScope(testDispatcher) - keyguardRepository = FakeKeyguardRepository() - bouncerRepository = FakeKeyguardBouncerRepository() + keyguardRepository = kosmos.fakeKeyguardRepository + bouncerRepository = kosmos.fakeKeyguardBouncerRepository commandQueue = FakeCommandQueue() - shadeRepository = FakeShadeRepository() - transitionRepository = spy(FakeKeyguardTransitionRepository()) + shadeRepository = kosmos.fakeShadeRepository + transitionRepository = spy(kosmos.fakeKeyguardTransitionRepository) powerInteractor = PowerInteractorFactory.create().powerInteractor - communalInteractor = - CommunalInteractorFactory.create(testScope = testScope).communalInteractor + communalInteractor = kosmos.communalInteractor whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) @@ -160,8 +164,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -184,8 +188,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -199,8 +203,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -210,8 +214,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingLockscreenHostedTransitionInteractor = FromDreamingLockscreenHostedTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -221,8 +225,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromAodTransitionInteractor = FromAodTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -232,8 +236,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromGoneTransitionInteractor = FromGoneTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -244,8 +248,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDozingTransitionInteractor = FromDozingTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -257,8 +261,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -269,8 +273,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -281,8 +285,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromGlanceableHubTransitionInteractor = FromGlanceableHubTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, glanceableHubTransitions = glanceableHubTransitions, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 6be92756ba8a..ba7927d6b8bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -25,8 +25,7 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase -import com.android.systemui.communal.data.repository.FakeCommunalRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController @@ -43,6 +42,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.testKosmos import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -79,6 +79,8 @@ import org.mockito.junit.MockitoJUnit @TestableLooper.RunWithLooper(setAsMainLooper = true) class MediaHierarchyManagerTest : SysuiTestCase() { + private val kosmos = testKosmos() + @Mock private lateinit var lockHost: MediaHost @Mock private lateinit var qsHost: MediaHost @Mock private lateinit var qqsHost: MediaHost @@ -110,10 +112,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var isQsBypassingShade: MutableStateFlow<Boolean> private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() - private val communalRepository = - FakeCommunalRepository(applicationScope = testScope.backgroundScope) - private val communalInteractor = - CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor + private val communalInteractor = kosmos.communalInteractor private val settings = FakeSettings() private lateinit var testableLooper: TestableLooper private lateinit var fakeHandler: FakeHandler diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index 0a464e6047d3..b701d7f315bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -21,6 +21,7 @@ import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.models.player.MediaViewHolder @@ -171,6 +172,38 @@ class MediaViewControllerTest : SysuiTestCase() { } @Test + fun testObtainViewState_expandedMatchesParentHeight() { + mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) + player.measureState = + TransitionViewState().apply { + this.height = 100 + this.measureHeight = 100 + } + mediaHostStateHolder.expandedMatchesParentHeight = true + mediaHostStateHolder.expansion = 1f + mediaHostStateHolder.measurementInput = + MeasurementInput( + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), + ) + + // Assign the height of each expanded layout + MediaViewHolder.backgroundIds.forEach { id -> + mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 100 + } + + mediaViewController.obtainViewState(mediaHostStateHolder) + + // Verify height of each expanded layout is updated to match constraint + MediaViewHolder.backgroundIds.forEach { id -> + assertTrue( + mediaViewController.expandedLayout.getConstraint(id).layout.mHeight == + ConstraintSet.MATCH_CONSTRAINT + ) + } + } + + @Test fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() { whenever(mockViewState.copy()).thenReturn(mockCopiedState) whenever(mockCopiedState.widgetStates) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 72847a6b6c45..c6cfabc61300 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -414,6 +414,18 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_verifyDeviceListCallback() { + // This test relies on mMediaOutputController.start being called while the selected device + // list has exactly one item, and that item's id is: + // - Different from both ids in mMediaDevices. + // - Different from the id of the route published by the device under test (usually the + // built-in speakers). + // So mock the selected device to respect these two preconditions. + MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class); + when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID); + doReturn(List.of(mockSelectedMediaDevice)) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + mMediaOutputController.start(mCb); reset(mCb); @@ -434,6 +446,18 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() { + // This test relies on mMediaOutputController.start being called while the selected device + // list has exactly one item, and that item's id is: + // - Different from both ids in mMediaDevices. + // - Different from the id of the route published by the device under test (usually the + // built-in speakers). + // So mock the selected device to respect these two preconditions. + MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class); + when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID); + doReturn(List.of(mockSelectedMediaDevice)) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + when(mMediaDevice1.getFeatures()).thenReturn( ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index b7a9ea751438..81ff817830ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -25,14 +25,16 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.compose.ComposeFacade import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -52,6 +54,8 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class GlanceableHubContainerControllerTest : SysuiTestCase() { + private val kosmos = testKosmos() + @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var shadeInteractor: ShadeInteractor @@ -71,9 +75,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - val withDeps = CommunalInteractorFactory.create() - communalInteractor = withDeps.communalInteractor - communalRepository = withDeps.communalRepository + communalInteractor = kosmos.communalInteractor + communalRepository = kosmos.fakeCommunalRepository underTest = GlanceableHubContainerController( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 11da2376328a..0bffa1c7de69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -57,7 +57,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -202,8 +201,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), shadeRepository, () -> sceneInteractor); - CommunalInteractor communalInteractor = - CommunalInteractorFactory.create().getCommunalInteractor(); + CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); FakeKeyguardTransitionRepository keyguardTransitionRepository = new FakeKeyguardTransitionRepository(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 8f46a37bf540..6fc88ce95325 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -43,7 +43,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; @@ -233,8 +232,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), mShadeRepository, () -> sceneInteractor); - CommunalInteractor communalInteractor = - CommunalInteractorFactory.create().getCommunalInteractor(); + CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); FakeKeyguardTransitionRepository keyguardTransitionRepository = new FakeKeyguardTransitionRepository(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 05e866e85112..13934dac2401 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -27,7 +27,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeCommandQueue @@ -144,7 +144,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { { fromLockscreenTransitionInteractor }, { fromPrimaryBouncerTransitionInteractor } ) - val communalInteractor = CommunalInteractorFactory.create().communalInteractor + val communalInteractor = kosmos.communalInteractor fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( keyguardTransitionRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt index df547ae5883e..350ed2d9ff22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -17,9 +17,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.os.UserHandle +import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -33,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -55,28 +58,31 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { val statusBarStateController: StatusBarStateController = mock() val keyguardStateController: KeyguardStateController = mock() val mSelectedUserInteractor: SelectedUserInteractor = mock() + val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController = + mock() val coordinator: SensitiveContentCoordinator = - DaggerTestSensitiveContentCoordinatorComponent - .factory() - .create( - dynamicPrivacyController, - lockscreenUserManager, - keyguardUpdateMonitor, - statusBarStateController, - keyguardStateController, - mSelectedUserInteractor) - .coordinator + DaggerTestSensitiveContentCoordinatorComponent.factory() + .create( + dynamicPrivacyController, + lockscreenUserManager, + keyguardUpdateMonitor, + statusBarStateController, + keyguardStateController, + mSelectedUserInteractor, + sensitiveNotificationProtectionController + ) + .coordinator @Test fun onDynamicPrivacyChanged_invokeInvalidationListener() { coordinator.attach(pipeline) - val invalidator = withArgCaptor<Invalidator> { - verify(pipeline).addPreRenderInvalidator(capture()) - } - val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> { - verify(dynamicPrivacyController).addListener(capture()) - } + val invalidator = + withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) } + val dynamicPrivacyListener = + withArgCaptor<DynamicPrivacyController.Listener> { + verify(dynamicPrivacyController).addListener(capture()) + } val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() invalidator.setInvalidationListener(invalidationListener) @@ -89,9 +95,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { @Test fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) @@ -105,11 +112,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, false) + } + + @Test fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) @@ -123,11 +178,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, false) + } + + @Test fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) @@ -141,17 +244,87 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, false) + } + + @Test fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -159,17 +332,92 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -179,15 +427,37 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { @Test fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -195,19 +465,96 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + val entry = fakeNotification(2, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + val entry = fakeNotification(2, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) val entry = fakeNotification(2, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -217,9 +564,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { @Test fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) @@ -227,9 +575,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD) whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any())) - .thenReturn(true) - + .thenReturn(true) val entry = fakeNotification(2, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any())) + .thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -237,15 +587,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry { - val mockUserHandle = mock<UserHandle>().apply { - whenever(identifier).thenReturn(notifUserId) - } - val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply { - whenever(user).thenReturn(mockUserHandle) - } - val mockEntry = mock<NotificationEntry>().apply { - whenever(sbn).thenReturn(mockSbn) - } + val mockUserHandle = + mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) } + val mockSbn: StatusBarNotification = + mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) } + val mockEntry = mock<NotificationEntry>().apply { whenever(sbn).thenReturn(mockSbn) } whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction) whenever(mockEntry.rowExists()).thenReturn(true) return object : ListEntry("key", 0) { @@ -268,6 +614,8 @@ interface TestSensitiveContentCoordinatorComponent { @BindsInstance statusBarStateController: StatusBarStateController, @BindsInstance keyguardStateController: KeyguardStateController, @BindsInstance selectedUserInteractor: SelectedUserInteractor, + @BindsInstance + sensitiveNotificationProtectionController: SensitiveNotificationProtectionController, ): TestSensitiveContentCoordinatorComponent } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 89f826b2049d..1ab4c32c7d08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING; import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -32,6 +33,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static kotlinx.coroutines.flow.FlowKt.emptyFlow; @@ -39,6 +41,7 @@ import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDis import android.metrics.LogMaker; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -101,6 +104,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.settings.SecureSettings; @@ -172,10 +176,16 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private ActivityStarter mActivityStarter; @Mock private KeyguardTransitionRepository mKeyguardTransitionRepo; @Mock private NotificationListViewBinder mViewBinder; + @Mock + private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController; + + @Captor + private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; + private final ActiveNotificationListRepository mActiveNotificationsRepository = new ActiveNotificationListRepository(); @@ -386,6 +396,23 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + public void testOnUserChange_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); initController(/* viewIsAttached= */ true); @@ -403,6 +430,80 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnUserChange_verifyNotSensitive_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnUserChange_verifySensitiveProfile_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnUserChange_verifySensitiveActive_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + public void testOnStatePostChange_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test public void testOnStatePostChange_verifyIfProfileIsPublic() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); @@ -418,6 +519,194 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_verifyNotSensitive_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_verifyIfProfileIsPublic_screenshareNotificationHidingEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_verifyIfSensitiveActive_screenshareNotificationHidingEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + public void testOnStatePostChange_goingFullShade_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, false); + } + + @Test + public void testOnStatePostChange_goingFullShade_verifyIfProfileIsPublic() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_goingFullShade_verifyNotSensitive_screenshareHideEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_goingFullShade_verifyProfileIsPublic_screenshareHideEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_goingFullShade_verifySensitiveActive_screenshareHideEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnProjectionStateChanged_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()) + .thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController) + .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture()); + + mSensitiveStateListenerArgumentCaptor.getValue().run(); + + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnProjectionStateChanged_verifyIfProfileIsPublic() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController) + .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture()); + + mSensitiveStateListenerArgumentCaptor.getValue().run(); + + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnProjectionStateChanged_verifyIfSensitiveActive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController) + .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture()); + + mSensitiveStateListenerArgumentCaptor.getValue().run(); + + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test public void testOnMenuShownLogging() { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker( @@ -666,6 +955,20 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean()); } + @Test + @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void sensitiveNotificationProtectionControllerListenerNotRegistered() { + initController(/* viewIsAttached= */ true); + verifyZeroInteractions(mSensitiveNotificationProtectionController); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void sensitiveNotificationProtectionControllerListenerRegistered() { + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any()); + } + private LogMaker logMatcher(int category, int type) { return argThat(new LogMatcher(category, type)); } @@ -744,7 +1047,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mSecureSettings, mock(NotificationDismissibilityProvider.class), mActivityStarter, - new ResourcesSplitShadeStateController()); + new ResourcesSplitShadeStateController(), + mSensitiveNotificationProtectionController); } static class LogMatcher implements ArgumentMatcher<LogMaker> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 4827c92ce452..d9eaea1367cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -66,6 +66,7 @@ import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -145,6 +146,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private AlternateBouncerToGoneTransitionViewModel mAlternateBouncerToGoneTransitionViewModel; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock private KeyguardInteractor mKeyguardInteractor; private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository(); @Mock private CoroutineDispatcher mMainDispatcher; @Mock private TypedArray mMockTypedArray; @@ -292,6 +294,7 @@ public class ScrimControllerTest extends SysuiTestCase { mPrimaryBouncerToGoneTransitionViewModel, mAlternateBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, + mKeyguardInteractor, mWallpaperRepository, mMainDispatcher, mLinearLargeScreenShadeInterpolator); @@ -1000,6 +1003,7 @@ public class ScrimControllerTest extends SysuiTestCase { mPrimaryBouncerToGoneTransitionViewModel, mAlternateBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, + mKeyguardInteractor, mWallpaperRepository, mMainDispatcher, mLinearLargeScreenShadeInterpolator); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt new file mode 100644 index 000000000000..cd5d5ed0d08e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt @@ -0,0 +1,231 @@ +/* + * 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.policy + +import android.app.Notification +import android.media.projection.MediaProjectionInfo +import android.media.projection.MediaProjectionManager +import android.os.Handler +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { + @Mock private lateinit var handler: Handler + + @Mock private lateinit var mediaProjectionManager: MediaProjectionManager + + @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo + + @Mock private lateinit var listener1: Runnable + @Mock private lateinit var listener2: Runnable + @Mock private lateinit var listener3: Runnable + + @Captor + private lateinit var mediaProjectionCallbackCaptor: + ArgumentCaptor<MediaProjectionManager.Callback> + + private lateinit var controller: SensitiveNotificationProtectionControllerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) + + controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler) + + // Obtain useful MediaProjectionCallback + verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any()) + } + + @Test + fun init_flagEnabled_registerMediaProjectionManagerCallback() { + assertNotNull(mediaProjectionCallbackCaptor.value) + } + + @Test + fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() { + mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) + reset(mediaProjectionManager) + + controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler) + + verifyZeroInteractions(mediaProjectionManager) + } + + @Test + fun registerSensitiveStateListener_singleListener() { + controller.registerSensitiveStateListener(listener1) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + } + + @Test + fun registerSensitiveStateListener_multipleListeners() { + controller.registerSensitiveStateListener(listener1) + controller.registerSensitiveStateListener(listener2) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + verify(listener2, times(2)).run() + } + + @Test + fun registerSensitiveStateListener_afterProjectionActive() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + controller.registerSensitiveStateListener(listener1) + verifyZeroInteractions(listener1) + + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1).run() + } + + @Test + fun unregisterSensitiveStateListener_singleListener() { + controller.registerSensitiveStateListener(listener1) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + + controller.unregisterSensitiveStateListener(listener1) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verifyNoMoreInteractions(listener1) + } + + @Test + fun unregisterSensitiveStateListener_multipleListeners() { + controller.registerSensitiveStateListener(listener1) + controller.registerSensitiveStateListener(listener2) + controller.registerSensitiveStateListener(listener3) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + verify(listener2, times(2)).run() + verify(listener3, times(2)).run() + + controller.unregisterSensitiveStateListener(listener1) + controller.unregisterSensitiveStateListener(listener2) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verifyNoMoreInteractions(listener1) + verifyNoMoreInteractions(listener2) + verify(listener3, times(4)).run() + } + + @Test + fun isSensitiveStateActive_projectionInactive_false() { + assertFalse(controller.isSensitiveStateActive) + } + + @Test + fun isSensitiveStateActive_projectionActive_true() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + assertTrue(controller.isSensitiveStateActive) + } + + @Test + fun isSensitiveStateActive_projectionInactiveAfterActive_false() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + assertFalse(controller.isSensitiveStateActive) + } + + @Test + fun isSensitiveStateActive_projectionActiveAfterInactive_true() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + assertTrue(controller.isSensitiveStateActive) + } + + @Test + fun shouldProtectNotification_projectionInactive_false() { + val notificationEntry = mock(NotificationEntry::class.java) + + assertFalse(controller.shouldProtectNotification(notificationEntry)) + } + + @Test + fun shouldProtectNotification_projectionActive_fgsNotification_false() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + val notificationEntry = mock(NotificationEntry::class.java) + val sbn = mock(StatusBarNotification::class.java) + val notification = mock(Notification::class.java) + `when`(notificationEntry.sbn).thenReturn(sbn) + `when`(sbn.notification).thenReturn(notification) + `when`(notification.isFgsOrUij).thenReturn(true) + + assertFalse(controller.shouldProtectNotification(notificationEntry)) + } + + @Test + fun shouldProtectNotification_projectionActive_notFgsNotification_true() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + val notificationEntry = mock(NotificationEntry::class.java) + val sbn = mock(StatusBarNotification::class.java) + val notification = mock(Notification::class.java) + `when`(notificationEntry.sbn).thenReturn(sbn) + `when`(sbn.notification).thenReturn(notification) + `when`(notification.isFgsOrUij).thenReturn(false) + + assertTrue(controller.shouldProtectNotification(notificationEntry)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 589f7c23ea13..744f4244215b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -100,7 +100,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; @@ -443,8 +442,7 @@ public class BubblesTest extends SysuiTestCase { () -> keyguardInteractor, () -> mFromLockscreenTransitionInteractor, () -> mFromPrimaryBouncerTransitionInteractor); - CommunalInteractor communalInteractor = - CommunalInteractorFactory.create().getCommunalInteractor(); + CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor( keyguardTransitionRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt deleted file mode 100644 index 1ba863052e2d..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.communal.domain.interactor - -import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository -import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository -import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository -import com.android.systemui.communal.widgets.CommunalAppWidgetHost -import com.android.systemui.communal.widgets.EditWidgetsActivityStarter -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository -import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.test.TestScope - -// TODO(b/319335645): get rid of me and use kosmos. -object CommunalInteractorFactory { - - @JvmOverloads - @JvmStatic - fun create( - testScope: TestScope = TestScope(), - communalRepository: FakeCommunalRepository = - FakeCommunalRepository(testScope.backgroundScope), - keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(), - widgetRepository: FakeCommunalWidgetRepository = - FakeCommunalWidgetRepository(testScope.backgroundScope), - mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(), - smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(), - tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(), - communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(), - appWidgetHost: CommunalAppWidgetHost = mock(), - editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(), - ): WithDependencies { - val keyguardInteractor = - KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor - val communalTutorialInteractor = - CommunalTutorialInteractor( - testScope.backgroundScope, - tutorialRepository, - keyguardInteractor, - communalRepository, - ) - - return WithDependencies( - testScope, - communalRepository, - widgetRepository, - communalPrefsRepository, - mediaRepository, - smartspaceRepository, - tutorialRepository, - keyguardRepository, - keyguardInteractor, - communalTutorialInteractor, - appWidgetHost, - editWidgetsActivityStarter, - CommunalInteractor( - testScope.backgroundScope, - communalRepository, - widgetRepository, - communalPrefsRepository, - mediaRepository, - smartspaceRepository, - keyguardInteractor, - appWidgetHost, - editWidgetsActivityStarter, - ), - ) - } - - data class WithDependencies( - val testScope: TestScope, - val communalRepository: FakeCommunalRepository, - val widgetRepository: FakeCommunalWidgetRepository, - val communalPrefsRepository: FakeCommunalPrefsRepository, - val mediaRepository: FakeCommunalMediaRepository, - val smartspaceRepository: FakeSmartspaceRepository, - val tutorialRepository: FakeCommunalTutorialRepository, - val keyguardRepository: FakeKeyguardRepository, - val keyguardInteractor: KeyguardInteractor, - val tutorialInteractor: CommunalTutorialInteractor, - val appWidgetHost: CommunalAppWidgetHost, - val editWidgetsActivityStarter: EditWidgetsActivityStarter, - val communalInteractor: CommunalInteractor, - ) -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 59f56dd18f0e..5766f7a9028c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -130,6 +130,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _isEncryptedOrLockdown = MutableStateFlow(true) override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown + override val topClippingBounds = MutableStateFlow<Int?>(null) + override fun setQuickSettingsVisible(isVisible: Boolean) { _isQuickSettingsVisible.value = isVisible } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index cc0449d7e7bb..321f94468a2e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -26,6 +26,7 @@ import com.android.systemui.classifier.falsingCollector import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -72,6 +73,7 @@ class KosmosJavaAdapter( val powerInteractor by lazy { kosmos.powerInteractor } val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } + val communalInteractor by lazy { kosmos.communalInteractor } init { kosmos.applicationContext = testCase.context diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt index e208add32efa..5476d5509c0c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt @@ -17,7 +17,15 @@ package com.android.systemui.statusbar.ui import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.statusbar.policy.configurationController -val Kosmos.systemBarUtilsState by - Kosmos.Fixture { SystemBarUtilsState(configurationController, systemBarUtilsProxy) } +val Kosmos.systemBarUtilsState by Fixture { + SystemBarUtilsState( + testDispatcher, + testDispatcher, + configurationController, + systemBarUtilsProxy, + ) +} diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 3483c1a1404a..a493d7a57500 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -95,6 +95,7 @@ public class SystemConfig { private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100; private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200; private static final int ALLOW_VENDOR_APEX = 0x400; + private static final int ALLOW_SIGNATURE_PERMISSIONS = 0x800; private static final int ALLOW_ALL = ~0; // property for runtime configuration differentiation @@ -597,7 +598,7 @@ public class SystemConfig { // Vendors are only allowed to customize these int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS - | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX; + | ALLOW_SIGNATURE_PERMISSIONS | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX; if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.O_MR1) { // For backward compatibility vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS); @@ -649,9 +650,9 @@ public class SystemConfig { // TODO(b/157203468): ALLOW_HIDDENAPI_WHITELISTING must be removed because we prohibited // the use of hidden APIs from the product partition. int productPermissionFlag = ALLOW_FEATURES | ALLOW_LIBS | ALLOW_PERMISSIONS - | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_HIDDENAPI_WHITELISTING - | ALLOW_ASSOCIATIONS | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS - | ALLOW_VENDOR_APEX; + | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_SIGNATURE_PERMISSIONS + | ALLOW_HIDDENAPI_WHITELISTING | ALLOW_ASSOCIATIONS + | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS | ALLOW_VENDOR_APEX; if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) { // TODO(b/157393157): This must check product interface enforcement instead of // DEVICE_INITIAL_SDK_INT for the devices without product interface enforcement. @@ -772,6 +773,8 @@ public class SystemConfig { final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0; final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) != 0; + final boolean allowSignaturePermissions = (permissionFlag & ALLOW_SIGNATURE_PERMISSIONS) + != 0; final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0; final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) != 0; @@ -1246,6 +1249,38 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); } } break; + case "signature-permissions": { + if (allowSignaturePermissions) { + // signature permissions from system, apex, vendor, product and + // system_ext partitions are stored separately. This is to + // prevent xml files in the vendor partition from granting + // permissions to signature apps in the system partition and vice versa. + boolean vendor = permFile.toPath().startsWith( + Environment.getVendorDirectory().toPath() + "/") + || permFile.toPath().startsWith( + Environment.getOdmDirectory().toPath() + "/"); + boolean product = permFile.toPath().startsWith( + Environment.getProductDirectory().toPath() + "/"); + boolean systemExt = permFile.toPath().startsWith( + Environment.getSystemExtDirectory().toPath() + "/"); + if (vendor) { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getVendorSignatureAppAllowlist()); + } else if (product) { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getProductSignatureAppAllowlist()); + } else if (systemExt) { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getSystemExtSignatureAppAllowlist()); + } else { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getSignatureAppAllowlist()); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + XmlUtils.skipCurrentTag(parser); + } + } break; case "oem-permissions": { if (allowOemPermissions) { readOemPermissions(parser); @@ -1655,6 +1690,12 @@ public class SystemConfig { readPermissionAllowlist(parser, allowlist, "privapp-permissions"); } + private void readSignatureAppPermissions(@NonNull XmlPullParser parser, + @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist) + throws IOException, XmlPullParserException { + readPermissionAllowlist(parser, allowlist, "signature-permissions"); + } + private void readInstallInUserType(XmlPullParser parser, Map<String, Set<String>> doInstallMap, Map<String, Set<String>> nonInstallMap) diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index fb4943a9f4ca..c1d5ebf6c4ec 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1328,8 +1328,7 @@ public class InputManagerService extends IInputManager.Stub mPointerIconDisplayContext = null; } - updateAdditionalDisplayInputProperties(displayId, - AdditionalDisplayInputProperties::reset); + updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset); // TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been // removed in InputDispatcher instead of this callback. @@ -1812,8 +1811,6 @@ public class InputManagerService extends IInputManager.Stub mPointerIconType = icon.getType(); mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null; - if (!mCurrentDisplayProperties.pointerIconVisible) return false; - return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken); } } @@ -3509,7 +3506,11 @@ public class InputManagerService extends IInputManager.Stub properties = new AdditionalDisplayInputProperties(); mAdditionalDisplayInputProperties.put(displayId, properties); } + final boolean oldPointerIconVisible = properties.pointerIconVisible; updater.accept(properties); + if (oldPointerIconVisible != properties.pointerIconVisible) { + mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible); + } if (properties.allDefaults()) { mAdditionalDisplayInputProperties.remove(displayId); } diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index c3ef80f3624a..706db3d37bba 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -190,6 +190,8 @@ interface NativeInputManagerService { boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId, @NonNull IBinder inputToken); + void setPointerIconVisibility(int displayId, boolean visible); + void requestPointerCapture(IBinder windowToken, boolean enabled); boolean canDispatchToDisplay(int deviceId, int displayId); @@ -452,6 +454,9 @@ interface NativeInputManagerService { int pointerId, IBinder inputToken); @Override + public native void setPointerIconVisibility(int displayId, boolean visible); + + @Override public native void requestPointerCapture(IBinder windowToken, boolean enabled); @Override diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index ac826afc1d22..68c95b16d318 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -18,6 +18,10 @@ package com.android.server.pm; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY; +import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static android.app.PendingIntent.FLAG_IMMUTABLE; @@ -43,6 +47,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.PendingIntent; import android.app.admin.DevicePolicyCache; @@ -213,6 +218,7 @@ public class LauncherAppsService extends SystemService { private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final ShortcutServiceInternal mShortcutServiceInternal; private final PackageManagerInternal mPackageManagerInternal; + private final AppOpsManager mAppOpsManager; private final PackageCallbackList<IOnAppsChangedListener> mListeners = new PackageCallbackList<IOnAppsChangedListener>(); private final DevicePolicyManager mDpm; @@ -253,6 +259,7 @@ public class LauncherAppsService extends SystemService { LocalServices.getService(ShortcutServiceInternal.class)); mPackageManagerInternal = Objects.requireNonNull( LocalServices.getService(PackageManagerInternal.class)); + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mShortcutServiceInternal.addListener(mPackageMonitor); mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal); mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler); @@ -1998,6 +2005,23 @@ public class LauncherAppsService extends SystemService { } } + @Override + public void setArchiveCompatibilityOptions(boolean enableIconOverlay, + boolean enableUnarchivalConfirmation) { + int callingUid = Binder.getCallingUid(); + Binder.withCleanCallingIdentity( + () -> { + mAppOpsManager.setUidMode( + OP_ARCHIVE_ICON_OVERLAY, + callingUid, + enableIconOverlay ? MODE_ALLOWED : MODE_IGNORED); + mAppOpsManager.setUidMode( + OP_UNARCHIVAL_CONFIRMATION, + callingUid, + enableUnarchivalConfirmation ? MODE_ALLOWED : MODE_IGNORED); + }); + } + /** Checks if user is a profile of or same as listeningUser. * and the user is enabled. */ private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user, diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 09a91eda483a..c1b3673865dc 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.START_ABORTED; import static android.app.ActivityManager.START_CLASS_NOT_FOUND; import static android.app.ActivityManager.START_PERMISSION_DENIED; import static android.app.ActivityManager.START_SUCCESS; +import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; @@ -31,6 +32,7 @@ import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; +import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE; import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -66,8 +68,11 @@ import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.LayerDrawable; import android.os.Binder; import android.os.Bundle; @@ -269,11 +274,12 @@ public class PackageArchiver { Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName)); try { - // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options. requestUnarchive(packageName, callerPackageName, getOrCreateLauncherListener(userId, packageName), UserHandle.of(userId), - false /* showUnarchivalConfirmation= */); + getAppOpsManager().checkOp( + AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName) + == MODE_ALLOWED); } catch (Throwable t) { Slog.e(TAG, TextUtils.formatSimple( "Unexpected error occurred while unarchiving package %s: %s.", packageName, @@ -379,9 +385,8 @@ public class PackageArchiver { verifyNotSystemApp(ps.getFlags()); verifyInstalled(ps, userId); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); - verifyInstaller(responsibleInstallerPackage, userId); - ApplicationInfo installerInfo = snapshot.getApplicationInfo( - responsibleInstallerPackage, /* flags= */ 0, userId); + ApplicationInfo installerInfo = verifyInstaller( + snapshot, responsibleInstallerPackage, userId); verifyOptOutStatus(packageName, UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId()))); @@ -421,10 +426,10 @@ public class PackageArchiver { List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); for (int i = 0, size = mainActivities.size(); i < size; ++i) { var mainActivity = mainActivities.get(i); - Path iconPath = storeDrawable( - packageName, mainActivity.getIcon(), userId, i, iconSize); - Path monochromePath = storeDrawable( - packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize); + Path iconPath = storeAdaptiveDrawable( + packageName, mainActivity.getIcon(), userId, i * 2 + 0, iconSize); + Path monochromePath = storeAdaptiveDrawable( + packageName, mainActivity.getMonochromeIcon(), userId, i * 2 + 1, iconSize); ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( mainActivity.getLabel().toString(), @@ -451,7 +456,8 @@ public class PackageArchiver { List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); for (int i = 0, size = mainActivities.size(); i < size; i++) { LauncherActivityInfo mainActivity = mainActivities.get(i); - Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize); + Path iconPath = storeIcon(packageName, mainActivity, userId, i * 2 + 0, iconSize); + // i * 2 + 1 reserved for monochromeIcon ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( mainActivity.getLabel().toString(), @@ -495,8 +501,30 @@ public class PackageArchiver { return iconFile.toPath(); } - private void verifyInstaller(String installerPackageName, int userId) - throws PackageManager.NameNotFoundException { + /** + * Create an <a + * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive"> + * adaptive icon</a> from an icon. + * This is necessary so the icon can be displayed properly by different launchers. + */ + private static Path storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable, + @UserIdInt int userId, int index, int iconSize) throws IOException { + if (iconDrawable == null) { + return null; + } + + // see BaseIconFactory#createShapedIconBitmap + float inset = getExtraInsetFraction(); + inset = inset / (1 + 2 * inset); + Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), + new InsetDrawable(iconDrawable/*d*/, inset, inset, inset, inset)); + + return storeDrawable(packageName, d, userId, index, iconSize); + } + + + private ApplicationInfo verifyInstaller(Computer snapshot, String installerPackageName, + int userId) throws PackageManager.NameNotFoundException { if (TextUtils.isEmpty(installerPackageName)) { throw new PackageManager.NameNotFoundException("No installer found"); } @@ -505,6 +533,12 @@ public class PackageArchiver { && !verifySupportsUnarchival(installerPackageName, userId)) { throw new PackageManager.NameNotFoundException("Installer does not support unarchival"); } + ApplicationInfo appInfo = snapshot.getApplicationInfo( + installerPackageName, /* flags=*/ 0, userId); + if (appInfo == null) { + throw new PackageManager.NameNotFoundException("Failed to obtain Installer info"); + } + return appInfo; } /** @@ -570,7 +604,7 @@ public class PackageArchiver { } try { - verifyInstaller(getResponsibleInstallerPackage(ps), userId); + verifyInstaller(snapshot, getResponsibleInstallerPackage(ps), userId); getLauncherActivityInfos(packageName, userId); } catch (PackageManager.NameNotFoundException e) { return false; @@ -762,7 +796,8 @@ public class PackageArchiver { * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple * launcher activities, only one of the icons is returned arbitrarily. */ - public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) { + public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user, + String callingPackageName) { Objects.requireNonNull(packageName); Objects.requireNonNull(user); @@ -785,7 +820,13 @@ public class PackageArchiver { // TODO(b/298452477) Handle monochrome icons. // In the rare case the archived app defined more than two launcher activities, we choose // the first one arbitrarily. - return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0))); + Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0)); + if (getAppOpsManager().checkOp( + AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName) + == MODE_ALLOWED) { + icon = includeCloudOverlay(icon); + } + return icon; } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 609b3aa3f43d..f09fa21792dd 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6388,8 +6388,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override - public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) { - return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user); + public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user, + @NonNull String callingPackageName) { + return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user, + callingPackageName); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index ca00c84da724..88dc60c3f70f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -297,6 +297,8 @@ class PackageManagerShellCommand extends ShellCommand { return runSetHiddenSetting(true); case "unhide": return runSetHiddenSetting(false); + case "unstop": + return runSetStoppedState(false); case "suspend": return runSuspend(true, 0); case "suspend-quarantine": @@ -2662,6 +2664,26 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runSetStoppedState(boolean state) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + final int translatedUserId = + translateUserId(userId, UserHandle.USER_NULL, "runSetStoppedState"); + mInterface.setPackageStoppedState(pkg, state, userId); + getOutPrintWriter().println("Package " + pkg + " new stopped state: " + + mInterface.isPackageStoppedForUser(pkg, translatedUserId)); + return 0; + } + private int runSetDistractingRestriction() { final PrintWriter pw = getOutPrintWriter(); int userId = UserHandle.USER_SYSTEM; @@ -4934,6 +4956,8 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT"); pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); pw.println(""); + pw.println(" unstop [--user USER_ID] PACKAGE"); + pw.println(""); pw.println(" suspend [--user USER_ID] PACKAGE [PACKAGE...]"); pw.println(" Suspends the specified package(s) (as user)."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java index 3efac81d44e3..d138606369b9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java +++ b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java @@ -26,6 +26,7 @@ import android.util.ArrayMap; public final class PermissionAllowlist { @NonNull private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist = new ArrayMap<>(); + @NonNull private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist = new ArrayMap<>(); @@ -43,6 +44,19 @@ public final class PermissionAllowlist { mApexPrivilegedAppAllowlists = new ArrayMap<>(); @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mSignatureAppAllowlist = + new ArrayMap<>(); + @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorSignatureAppAllowlist = + new ArrayMap<>(); + @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mProductSignatureAppAllowlist = + new ArrayMap<>(); + @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtSignatureAppAllowlist = + new ArrayMap<>(); + + @NonNull public ArrayMap<String, ArrayMap<String, Boolean>> getOemAppAllowlist() { return mOemAppAllowlist; } @@ -73,6 +87,26 @@ public final class PermissionAllowlist { return mApexPrivilegedAppAllowlists; } + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getSignatureAppAllowlist() { + return mSignatureAppAllowlist; + } + + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getVendorSignatureAppAllowlist() { + return mVendorSignatureAppAllowlist; + } + + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getProductSignatureAppAllowlist() { + return mProductSignatureAppAllowlist; + } + + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getSystemExtSignatureAppAllowlist() { + return mSystemExtSignatureAppAllowlist; + } + @Nullable public Boolean getOemAppAllowlistState(@NonNull String packageName, @NonNull String permissionName) { @@ -137,4 +171,44 @@ public final class PermissionAllowlist { } return permissions.get(permissionName); } + + @Nullable + public Boolean getSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } + + @Nullable + public Boolean getVendorSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mVendorSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } + + @Nullable + public Boolean getProductSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mProductSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } + + @Nullable + public Boolean getSystemExtSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mSystemExtSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 1c90e30c256e..ce0efe1aba49 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -304,6 +304,7 @@ public: bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId, DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken); + void setPointerIconVisibility(int32_t displayId, bool visible); void setMotionClassifierEnabled(bool enabled); std::optional<std::string> getBluetoothAddress(int32_t deviceId); void setStylusButtonMotionEventsEnabled(bool enabled); @@ -1397,6 +1398,13 @@ bool NativeInputManager::setPointerIcon( return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId); } +void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) { + if (!ENABLE_POINTER_CHOREOGRAPHER) { + return; + } + mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible); +} + TouchAffineTransformation NativeInputManager::getTouchAffineTransformation( JNIEnv *env, jfloatArray matrixArr) { ATRACE_CALL(); @@ -2550,6 +2558,13 @@ static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject ico ibinderForJavaObject(env, inputTokenObj)); } +static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, jint displayId, + jboolean visible) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + + im->setPointerIconVisibility(displayId, visible); +} + static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -2828,6 +2843,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeSetCustomPointerIcon}, {"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z", (void*)nativeSetPointerIcon}, + {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility}, {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay}, {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged}, {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation}, diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 62d2d7ee848a..4c74878dab70 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -22,6 +22,7 @@ import android.content.pm.PermissionGroupInfo import android.content.pm.PermissionInfo import android.content.pm.SigningDetails import android.os.Build +import android.permission.flags.Flags import android.util.Slog import com.android.internal.os.RoSystemProperties import com.android.internal.pm.permission.CompatibilityPermissionInfo @@ -1197,15 +1198,80 @@ class AppIdPermissionPolicy : SchemePolicy() { newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!! .androidPackage!! .signingDetails - return sourceSigningDetails?.hasCommonSignerWithCapability( - packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION - ) == true || - packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || - platformSigningDetails.checkCapability( + val hasCommonSigner = + sourceSigningDetails?.hasCommonSignerWithCapability( packageSigningDetails, SigningDetails.CertCapabilities.PERMISSION - ) + ) == true || + packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || + platformSigningDetails.checkCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) + if (!Flags.signaturePermissionAllowlistEnabled()) { + return hasCommonSigner; + } + if (!hasCommonSigner) { + return false + } + // A platform signature permission also needs to be allowlisted on non-debuggable builds. + if (permission.packageName == PLATFORM_PACKAGE_NAME) { + val isRequestedByFactoryApp = + if (packageState.isSystem) { + // For updated system applications, a signature permission still needs to be + // allowlisted if it wasn't requested by the original application. + if (packageState.isUpdatedSystemApp) { + val disabledSystemPackage = + newState.externalState.disabledSystemPackageStates[ + packageState.packageName] + ?.androidPackage + disabledSystemPackage != null && + permission.name in disabledSystemPackage.requestedPermissions + } else { + true + } + } else { + false + } + if ( + !(isRequestedByFactoryApp || + getSignaturePermissionAllowlistState(packageState, permission.name) == true) + ) { + Slog.w( + LOG_TAG, + "Signature permission ${permission.name} for package" + + " ${packageState.packageName} (${packageState.path}) not in" + + " signature permission allowlist" + ) + if (!Build.isDebuggable()) { + return false + } + } + } + return true + } + + private fun MutateStateScope.getSignaturePermissionAllowlistState( + packageState: PackageState, + permissionName: String + ): Boolean? { + val permissionAllowlist = newState.externalState.permissionAllowlist + val packageName = packageState.packageName + return when { + packageState.isVendor || packageState.isOdm -> + permissionAllowlist.getVendorSignatureAppAllowlistState(packageName, permissionName) + packageState.isProduct -> + permissionAllowlist.getProductSignatureAppAllowlistState( + packageName, + permissionName + ) + packageState.isSystemExt -> + permissionAllowlist.getSystemExtSignatureAppAllowlistState( + packageName, + permissionName + ) + else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName) + } } private fun MutateStateScope.checkPrivilegedPermissionAllowlist( diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java index d82e6abeb502..23e3e2560524 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -26,18 +26,13 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; -import org.junit.runner.RunWith; import java.io.File; import java.util.List; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class AdditionalSubtypeUtilsTest { +public final class AdditionalSubtypeUtilsTest { @Test public void testSaveAndLoad() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java index 6eedeea8c333..b7223d6615b9 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -19,17 +19,11 @@ package com.android.server.inputmethod; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - import org.junit.Test; -import org.junit.runner.RunWith; import java.util.Arrays; import java.util.List; -@SmallTest -@RunWith(AndroidJUnit4.class) public final class HardwareKeyboardShortcutControllerTest { @Test diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index 0884b784ac73..fbe384a62658 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * 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. @@ -28,22 +28,16 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; import org.junit.Test; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class InputMethodSubtypeSwitchingControllerTest { +public final class InputMethodSubtypeSwitchingControllerTest { private static final String DUMMY_PACKAGE_NAME = "dummy package name"; private static final String DUMMY_IME_LABEL = "dummy ime label"; private static final String DUMMY_SETTING_ACTIVITY_NAME = ""; diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index 9688ef6cc83b..ac485be5b5a1 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * 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. @@ -41,15 +41,12 @@ import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.inputmethod.StartInputFlags; import com.google.common.truth.Truth; import org.junit.Test; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Collections; @@ -57,9 +54,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class InputMethodUtilsTest { +public final class InputMethodUtilsTest { private static final boolean IS_AUX = true; private static final boolean IS_DEFAULT = true; private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true; diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java index 01f8129c2cd7..d0b46f58d626 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -22,18 +22,12 @@ import static org.junit.Assert.assertEquals; import android.os.LocaleList; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - import org.junit.Test; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Locale; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class LocaleUtilsTest { +public final class LocaleUtilsTest { private static final LocaleUtils.LocaleExtractor<Locale> sIdentityMapper = source -> source; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java index 978044045ab3..a185ad99a8f3 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java @@ -419,7 +419,7 @@ public class PackageUserStateTest { "installerTitle"); packageUserState.setArchiveState(archiveState); assertEquals(archiveState, packageUserState.getArchiveState()); - assertTrue(archiveState.getArchiveTimeMillis() > currentTimeMillis); + assertTrue(archiveState.getArchiveTimeMillis() >= currentTimeMillis); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index ec7e35982311..e989d7b060be 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -552,20 +552,22 @@ public class PackageArchiverTest { when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( null); - assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull(); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT, + CALLER_PACKAGE)).isNull(); } @Test public void getArchivedAppIcon_notArchived() { - assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull(); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT, + CALLER_PACKAGE)).isNull(); } @Test public void getArchivedAppIcon_success() { mUserState.setArchiveState(createArchiveState()).setInstalled(false); - assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo( - mIcon); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT, + CALLER_PACKAGE)).isEqualTo(mIcon); } diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 080548520b0c..81df597f3f33 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -157,6 +157,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return mMockDevicePolicyManager; case Context.APP_SEARCH_SERVICE: case Context.ROLE_SERVICE: + case Context.APP_OPS_SERVICE: // RoleManager is final and cannot be mocked, so we only override the inject // accessor methods in ShortcutService. return getTestContext().getSystemService(name); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index ea948ca0e28b..863cda4905f1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -118,12 +118,19 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // Constructors that should be used to create instances of specific classes. Overrides scoring. private static final ImmutableMap<Class<?>, Constructor<?>> PREFERRED_CONSTRUCTORS; + // Setter methods that receive String parameters, but where those Strings represent Uris + // (and are visited/validated). + private static final ImmutableSet<Method> SETTERS_WITH_STRING_AS_URI; + static { try { PREFERRED_CONSTRUCTORS = ImmutableMap.of( Notification.Builder.class, Notification.Builder.class.getConstructor(Context.class, String.class)); + SETTERS_WITH_STRING_AS_URI = ImmutableSet.of( + Person.Builder.class.getMethod("setUri", String.class)); + EXCLUDED_SETTERS_OVERLOADS = ImmutableMultimap.<Class<?>, Method>builder() .put(RemoteViews.class, // b/245950570: Tries to connect to service and will crash. @@ -257,7 +264,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { @Nullable Class<?> styleClass, @Nullable Class<?> extenderClass, @Nullable Class<?> actionExtenderClass, boolean includeRemoteViews) { SpecialParameterGenerator specialGenerator = new SpecialParameterGenerator(context); - Set<Class<?>> excludedClasses = includeRemoteViews + ImmutableSet<Class<?>> excludedClasses = includeRemoteViews ? ImmutableSet.of() : ImmutableSet.of(RemoteViews.class); Location location = Location.root(Notification.Builder.class); @@ -294,7 +301,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static Object generateObject(Class<?> clazz, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { if (excludingClasses.contains(clazz)) { throw new IllegalArgumentException( String.format("Asked to generate a %s but it's part of the excluded set (%s)", @@ -369,7 +376,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static Object constructEmpty(Class<?> clazz, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { Constructor<?> bestConstructor; if (PREFERRED_CONSTRUCTORS.containsKey(clazz)) { // Use the preferred constructor. @@ -431,7 +438,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static void invokeAllSetters(Object instance, Location where, boolean allOverloads, - boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes, + boolean includingVoidMethods, ImmutableSet<Class<?>> excludingParameterTypes, SpecialParameterGenerator specialGenerator) { for (Method setter : ReflectionUtils.getAllSetters(instance.getClass(), where, allOverloads, includingVoidMethods, excludingParameterTypes)) { @@ -462,24 +469,34 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static Object[] generateParameters(Executable executable, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable) + " in " + where); Type[] parameterTypes = executable.getGenericParameterTypes(); Object[] parameterValues = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { - parameterValues[i] = generateParameter( - parameterTypes[i], + boolean generateUriAsString = false; + Type parameterType = parameterTypes[i]; + if (SETTERS_WITH_STRING_AS_URI.contains(executable) + && parameterType.equals(String.class)) { + generateUriAsString = true; + } + Object value = generateParameter( + generateUriAsString ? Uri.class : parameterType, where.plus(executable, String.format("[%d,%s]", i, parameterTypes[i].getTypeName())), excludingClasses, specialGenerator); + if (generateUriAsString) { + value = ((Uri) value).toString(); + } + parameterValues[i] = value; } return parameterValues; } private static Object generateParameter(Type parameterType, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { if (parameterType instanceof Class<?> parameterClass) { return generateObject( parameterClass, @@ -487,7 +504,8 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { excludingClasses, specialGenerator); } else if (parameterType instanceof ParameterizedType parameterizedType) { - if (parameterizedType.getRawType().equals(List.class) + if ((parameterizedType.getRawType().equals(List.class) + || parameterizedType.getRawType().equals(ArrayList.class)) && parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) { ArrayList listValue = new ArrayList(); for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) { @@ -503,12 +521,14 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static class ReflectionUtils { - static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) { - return Arrays.stream(containerClass.getDeclaredClasses()) - .filter( - innerClass -> clazz.isAssignableFrom(innerClass) - && !Modifier.isAbstract(innerClass.getModifiers())) - .collect(Collectors.toSet()); + static ImmutableSet<Class<?>> getConcreteSubclasses(Class<?> clazz, + Class<?> containerClass) { + return ImmutableSet.copyOf( + Arrays.stream(containerClass.getDeclaredClasses()) + .filter( + innerClass -> clazz.isAssignableFrom(innerClass) + && !Modifier.isAbstract(innerClass.getModifiers())) + .collect(Collectors.toSet())); } static String methodToString(Executable executable) { @@ -611,9 +631,16 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static class SpecialParameterGenerator { + + private static final ImmutableSet<Class<?>> INTERESTING_CLASSES_WITH_SPECIAL_GENERATION = + ImmutableSet.of(Uri.class, Icon.class, Intent.class, PendingIntent.class, + RemoteViews.class); + private static final ImmutableSet<Class<?>> INTERESTING_CLASSES = - ImmutableSet.of(Person.class, Uri.class, Icon.class, Intent.class, - PendingIntent.class, RemoteViews.class); + new ImmutableSet.Builder<Class<?>>() + .addAll(INTERESTING_CLASSES_WITH_SPECIAL_GENERATION) + .add(Person.class) // Constructed via reflection, but high-score. + .build(); private static final ImmutableSet<Class<?>> MOCKED_CLASSES = ImmutableSet.of(); private static final ImmutableMap<Class<?>, Object> PRIMITIVE_VALUES = @@ -637,7 +664,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } static boolean canGenerate(Class<?> clazz) { - return INTERESTING_CLASSES.contains(clazz) + return INTERESTING_CLASSES_WITH_SPECIAL_GENERATION.contains(clazz) || MOCKED_CLASSES.contains(clazz) || clazz.equals(Context.class) || clazz.equals(Bundle.class) @@ -672,17 +699,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { return Icon.createWithContentUri(iconUri); } - if (clazz == Person.class) { - // TODO(b/310189261): Person.setUri takes a string instead of a URI. We should - // find a way to use the SpecialParameterGenerator instead of this custom one. - Uri personUri = generateUri( - where.plus(Person.Builder.class).plus("setUri", String.class)); - Uri iconUri = generateUri(where.plus(Person.Builder.class).plus("setIcon", - Icon.class).plus(Icon.class).plus("createWithContentUri", Uri.class)); - return new Person.Builder().setUri(personUri.toString()).setIcon( - Icon.createWithContentUri(iconUri)).setName("John Doe").build(); - } - if (clazz == Intent.class) { return new Intent("action", generateUri(where.plus(Intent.class))); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 1c57623442c5..29faed195b4e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -132,8 +132,10 @@ import org.junit.runner.RunWith; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -563,6 +565,86 @@ public class ActivityStarterTests extends WindowTestsBase { return Pair.create(splitPrimaryActivity, splitSecondActivity); } + /** + * This test ensures that if the intent is being delivered to a desktop mode unfocused task + * while it is already on top, reports it as delivering to top. + */ + @Test + public void testDesktopModeDeliverToTop() { + final ActivityStarter starter = prepareStarter( + FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, + false /* mockGetRootTask */); + final List<ActivityRecord> activities = createActivitiesInDesktopMode(); + + // Set focus back to the first task. + activities.get(0).moveFocusableActivityToTop("testDesktopModeDeliverToTop"); + + // Start activity and delivered new intent. + starter.getIntent().setComponent(activities.get(3).mActivityComponent); + doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any()); + final int result = starter.setReason("testDesktopModeDeliverToTop").execute(); + + // Ensure result is delivering intent to top. + assertEquals(START_DELIVERED_TO_TOP, result); + } + + /** + * This test ensures that if the intent is being delivered to a desktop mode unfocused task + * reports it is brought to front instead of delivering to top. + */ + @Test + public void testDesktopModeTaskToFront() { + final ActivityStarter starter = prepareStarter( + FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false); + final List<ActivityRecord> activities = createActivitiesInDesktopMode(); + final ActivityRecord desktopModeFocusActivity = activities.get(0); + final ActivityRecord desktopModeReusableActivity = activities.get(1); + final ActivityRecord desktopModeTopActivity = new ActivityBuilder(mAtm).setCreateTask(true) + .setParentTask(desktopModeReusableActivity.getRootTask()).build(); + assertTrue(desktopModeTopActivity.inMultiWindowMode()); + + // Let first stack has focus. + desktopModeFocusActivity.moveFocusableActivityToTop("testDesktopModeTaskToFront"); + + // Start activity and delivered new intent. + starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent); + doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any()); + final int result = starter.setReason("testDesktopModeMoveToFront").execute(); + + // Ensure result is moving task to front. + assertEquals(START_TASK_TO_FRONT, result); + } + + /** Returns 4 activities. */ + private List<ActivityRecord> createActivitiesInDesktopMode() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + List<ActivityRecord> activityRecords = new ArrayList<>(); + + for (int i = 0; i < 4; i++) { + Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds()); + bounds.offset(20 * i, 20 * i); + desktopOrganizer.createTask(bounds); + } + + for (int i = 0; i < 4; i++) { + activityRecords.add(new TaskBuilder(mSupervisor) + .setParentTask(desktopOrganizer.mTasks.get(i)) + .setCreateActivity(true) + .build() + .getTopMostActivity()); + } + + for (int i = 0; i < 4; i++) { + activityRecords.get(i).setVisibleRequested(true); + } + + for (int i = 0; i < 4; i++) { + assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask()); + } + + return activityRecords; + } + @Test public void testMoveVisibleTaskToFront() { final ActivityRecord activity = new TaskBuilder(mSupervisor) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 114b9c3a68f2..c7c791337bb4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -20,6 +20,7 @@ import static android.app.AppOpsManager.OP_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -133,7 +134,9 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** Common base class for window manager unit test classes. */ class WindowTestsBase extends SystemServiceTestsBase { @@ -1892,6 +1895,55 @@ class WindowTestsBase extends SystemServiceTestsBase { } } + static class TestDesktopOrganizer extends WindowOrganizerTests.StubOrganizer { + final int mDesktopModeDefaultWidthDp = 840; + final int mDesktopModeDefaultHeightDp = 630; + final int mDesktopDensity = 284; + + final ActivityTaskManagerService mService; + final TaskDisplayArea mDefaultTDA; + List<Task> mTasks; + final DisplayContent mDisplay; + Rect mStableBounds; + + TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) { + mService = service; + mDefaultTDA = display.getDefaultTaskDisplayArea(); + mDisplay = display; + mService.mTaskOrganizerController.registerTaskOrganizer(this); + mTasks = new ArrayList<>(); + mStableBounds = display.getBounds(); + } + + TestDesktopOrganizer(ActivityTaskManagerService service) { + this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay()); + } + + public Task createTask(Rect bounds) { + Task task = mService.mTaskOrganizerController.createRootTask( + mDisplay, WINDOWING_MODE_FREEFORM, null); + task.setBounds(bounds); + mTasks.add(task); + spyOn(task); + return task; + } + + public Rect getDefaultDesktopTaskBounds() { + int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f); + int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f); + Rect outBounds = new Rect(); + + outBounds.set(0, 0, width, height); + // Center the task in stable bounds + outBounds.offset( + mStableBounds.centerX() - outBounds.centerX(), + mStableBounds.centerY() - outBounds.centerY() + ); + return outBounds; + } + + } + static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { return createTestWindowToken(type, dc, false /* persistOnEmpty */); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 9ec5f7a77b2c..67bb1f03cad6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6888,6 +6888,7 @@ public class TelephonyManager { } } + // TODO(b/316183370): replace all @code with @link in javadoc after feature is released /** * @return true if the current device is "voice capable". * <p> @@ -6901,7 +6902,10 @@ public class TelephonyManager { * PackageManager.FEATURE_TELEPHONY system feature, which is available * on any device with a telephony radio, even if the device is * data-only. - * @deprecated Replaced by {@link #isDeviceVoiceCapable()} + * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice + * capability may also be overridden by carriers for a given subscription. For voice capable + * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for + * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details. */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING) @Deprecated @@ -6923,9 +6927,10 @@ public class TelephonyManager { * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony * radio, even if the device is data-only. * <p> - * To check if a subscription is "voice capable", call method - * {@link SubscriptionInfo#getServiceCapabilities()} and compare the result with - * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}. + * Starting from Android 15, voice capability may also be overridden by carrier for a given + * subscription on a voice capable device. To check if a subscription is "voice capable", + * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if + * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ @@ -6943,7 +6948,10 @@ public class TelephonyManager { * <p> * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are * disabled when device doesn't support sms. - * @deprecated Replaced by {@link #isDeviceSmsCapable()} + * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS + * capability may also be overridden by carriers for a given subscription. For SMS capable + * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for + * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details. */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING) public boolean isSmsCapable() { @@ -6961,9 +6969,10 @@ public class TelephonyManager { * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are * disabled when device doesn't support SMS. * <p> - * To check if a subscription is "SMS capable", call method - * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with - * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}. + * Starting from Android 15, SMS capability may also be overridden by carriers for a given + * subscription on an SMS capable device. To check if a subscription is "SMS capable", + * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if + * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index b05863188beb..60a9f0b4332b 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -292,11 +292,13 @@ class InputManagerServiceTests { setVirtualMousePointerDisplayIdAndVerify(10) localService.setPointerIconVisible(false, 10) + verify(native).setPointerIconVisibility(10, false) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) localService.setMousePointerAccelerationEnabled(false, 10) verify(native).setMousePointerAccelerationEnabled(eq(false)) service.onDisplayRemoved(10) + verify(native).setPointerIconVisibility(10, true) verify(native).displayRemoved(eq(10)) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) verify(native).setMousePointerAccelerationEnabled(true) @@ -315,17 +317,20 @@ class InputManagerServiceTests { localService.setPointerIconVisible(false, 10) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) + verify(native).setPointerIconVisibility(10, false) localService.setMousePointerAccelerationEnabled(false, 10) verify(native).setMousePointerAccelerationEnabled(eq(false)) localService.setPointerIconVisible(true, 10) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) + verify(native).setPointerIconVisibility(10, true) localService.setMousePointerAccelerationEnabled(true, 10) verify(native).setMousePointerAccelerationEnabled(eq(true)) // Verify that setting properties on a different display is not propagated until the // pointer is moved to that display. localService.setPointerIconVisible(false, 20) + verify(native).setPointerIconVisibility(20, false) localService.setMousePointerAccelerationEnabled(false, 20) verifyNoMoreInteractions(native) @@ -341,6 +346,7 @@ class InputManagerServiceTests { localService.setPointerIconVisible(false, 10) localService.setMousePointerAccelerationEnabled(false, 10) + verify(native).setPointerIconVisibility(10, false) verifyNoMoreInteractions(native) setVirtualMousePointerDisplayIdAndVerify(10) |