diff options
151 files changed, 3222 insertions, 1991 deletions
diff --git a/Android.bp b/Android.bp index 3f06ffd7ecd8..b0e0b35a1f76 100644 --- a/Android.bp +++ b/Android.bp @@ -1133,16 +1133,6 @@ aidl_mapping { output: "framework-aidl-mappings.txt", } -genrule { - name: "framework-annotation-proc-index", - srcs: [":framework-annotation-proc"], - cmd: "unzip -qp $(in) unsupportedappusage/unsupportedappusage_index.csv > $(out)", - out: ["unsupportedappusage_index.csv"], - dist: { - targets: ["droidcore"], - }, -} - // Avoid including Parcelable classes as we don't want to have two copies of // Parcelable cross the libraries. This is used by telephony-common (frameworks/opt/telephony) // and TeleService app (packages/services/Telephony). diff --git a/apex/Android.bp b/apex/Android.bp index cd34f98690d5..e8f6e6bf2c46 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -84,6 +84,24 @@ stubs_defaults { }, } +java_defaults { + name: "framework-module-stubs-lib-defaults-publicapi", + installable: false, + sdk_version: "module_current", +} + +java_defaults { + name: "framework-module-stubs-lib-defaults-systemapi", + installable: false, + sdk_version: "module_current", +} + +java_defaults { + name: "framework-module-stubs-lib-defaults-module_libs_api", + installable: false, + sdk_version: "module_current", +} + // The defaults for module_libs comes in two parts - defaults for API checks // and defaults for stub generation. This is because we want the API txt // files to *only* include the module_libs_api, but the stubs to include diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING index 8fbfb1daaf6f..d99830dc47c9 100644 --- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING @@ -7,6 +7,7 @@ ], "options": [ {"include-filter": "com.android.server.DeviceIdleControllerTest"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING index bc7a7d3bef7d..b76c582cf287 100644 --- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING @@ -4,6 +4,7 @@ "name": "FrameworksMockingServicesTests", "options": [ {"include-filter": "com.android.server.DeviceIdleControllerTest"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING index e2e118074aac..484fec31e594 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING @@ -3,6 +3,7 @@ { "name": "CtsJobSchedulerTestCases", "options": [ + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.LargeTest"} ] @@ -11,6 +12,7 @@ "name": "FrameworksMockingServicesTests", "options": [ {"include-filter": "com.android.server.job"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] }, @@ -18,6 +20,7 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.job"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index ba7572a5fb44..c5dc51cc9c24 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -4,6 +4,7 @@ "name": "CtsUsageStatsTestCases", "options": [ {"include-filter": "android.app.usage.cts.UsageStatsTest"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] }, @@ -11,6 +12,7 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.usage"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 821dd9e46c19..99e82e7a3367 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -132,19 +132,19 @@ droidstubs { java_library { name: "framework-media-stubs-publicapi", srcs: [":framework-media-stubs-srcs-publicapi"], - sdk_version: "current", + defaults: ["framework-module-stubs-lib-defaults-publicapi"], } java_library { name: "framework-media-stubs-systemapi", srcs: [":framework-media-stubs-srcs-systemapi"], - sdk_version: "system_current", + defaults: ["framework-module-stubs-lib-defaults-systemapi"], } java_library { name: "framework-media-stubs-module_libs_api", srcs: [":framework-media-stubs-srcs-module_libs_api"], - sdk_version: "system_current", + defaults: ["framework-module-stubs-lib-defaults-module_libs_api"], } java_library { diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index 02c55b7c8df9..5f86ed621084 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -52,6 +52,7 @@ import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.ColorInfo; import java.io.EOFException; @@ -60,6 +61,7 @@ import java.io.InterruptedIOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -688,12 +690,83 @@ public final class MediaParser { * Returns an immutable list with the names of the parsers that are suitable for container * formats with the given {@link MediaFormat}. * - * <p>TODO: List which properties are taken into account. E.g. MimeType. + * <p>A parser supports a {@link MediaFormat} if the mime type associated with {@link + * MediaFormat#KEY_MIME} corresponds to the supported container format. + * + * @param mediaFormat The {@link MediaFormat} to check support for. + * @return The parser names that support the given {@code mediaFormat}, or the list of all + * parsers available if no container specific format information is provided. */ @NonNull @ParserName public static List<String> getParserNames(@NonNull MediaFormat mediaFormat) { - throw new UnsupportedOperationException(); + String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); + mimeType = mimeType == null ? null : Util.toLowerInvariant(mimeType.trim()); + if (TextUtils.isEmpty(mimeType)) { + // No MIME type provided. Return all. + return Collections.unmodifiableList( + new ArrayList<>(EXTRACTOR_FACTORIES_BY_NAME.keySet())); + } + ArrayList<String> result = new ArrayList<>(); + switch (mimeType) { + case "video/x-matroska": + case "audio/x-matroska": + case "video/x-webm": + case "audio/x-webm": + result.add(PARSER_NAME_MATROSKA); + break; + case "video/mp4": + case "audio/mp4": + case "application/mp4": + result.add(PARSER_NAME_MP4); + result.add(PARSER_NAME_FMP4); + break; + case "audio/mpeg": + result.add(PARSER_NAME_MP3); + break; + case "audio/aac": + result.add(PARSER_NAME_ADTS); + break; + case "audio/ac3": + result.add(PARSER_NAME_AC3); + break; + case "video/mp2t": + case "audio/mp2t": + result.add(PARSER_NAME_TS); + break; + case "video/x-flv": + result.add(PARSER_NAME_FLV); + break; + case "video/ogg": + case "audio/ogg": + case "application/ogg": + result.add(PARSER_NAME_OGG); + break; + case "video/mp2p": + case "video/mp1s": + result.add(PARSER_NAME_PS); + break; + case "audio/vnd.wave": + case "audio/wav": + case "audio/wave": + case "audio/x-wav": + result.add(PARSER_NAME_WAV); + break; + case "audio/amr": + result.add(PARSER_NAME_AMR); + break; + case "audio/ac4": + result.add(PARSER_NAME_AC4); + break; + case "audio/flac": + case "audio/x-flac": + result.add(PARSER_NAME_FLAC); + break; + default: + // No parsers support the given mime type. Do nothing. + break; + } + return Collections.unmodifiableList(result); } // Private fields. diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp index 86f4ab7c1128..707113b9672c 100644 --- a/apex/sdkextensions/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -86,7 +86,7 @@ droidstubs { java_library { name: "framework-sdkextensions-stubs-publicapi", srcs: [":framework-sdkextensions-stubs-srcs-publicapi"], - sdk_version: "current", + defaults: ["framework-module-stubs-lib-defaults-publicapi"], visibility: [ "//frameworks/base", // Framework "//frameworks/base/apex/sdkextensions", // sdkextensions SDK @@ -96,7 +96,7 @@ java_library { java_library { name: "framework-sdkextensions-stubs-systemapi", srcs: [":framework-sdkextensions-stubs-srcs-systemapi"], - sdk_version: "system_current", + defaults: ["framework-module-stubs-lib-defaults-systemapi"], visibility: [ "//frameworks/base", // Framework "//frameworks/base/apex/sdkextensions", // sdkextensions SDK @@ -106,7 +106,7 @@ java_library { java_library { name: "framework-sdkextensions-stubs-module_libs_api", srcs: [":framework-sdkextensions-stubs-srcs-module_libs_api"], - sdk_version: "system_current", + defaults: ["framework-module-stubs-lib-defaults-module_libs_api"], visibility: [ "//frameworks/base", // Framework "//frameworks/base/apex/sdkextensions", // sdkextensions SDK diff --git a/api/current.txt b/api/current.txt index 680b2248249a..c7e2e6f9c399 100644 --- a/api/current.txt +++ b/api/current.txt @@ -25341,11 +25341,12 @@ package android.media { public final class MediaCodec.QueueRequest { method public void queue(); method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer); + method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, @NonNull android.media.MediaCodec.CryptoInfo); method @NonNull public android.media.MediaCodec.QueueRequest setFlags(int); method @NonNull public android.media.MediaCodec.QueueRequest setFloatParameter(@NonNull String, float); method @NonNull public android.media.MediaCodec.QueueRequest setHardwareBuffer(@NonNull android.hardware.HardwareBuffer); method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int); - method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, @Nullable android.media.MediaCodec.CryptoInfo); + method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int); method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long); method @NonNull public android.media.MediaCodec.QueueRequest setPresentationTimeUs(long); method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String); diff --git a/api/system-current.txt b/api/system-current.txt index a1fd0dbd8df0..7e25382f06d9 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6061,7 +6061,7 @@ package android.net { method public void release(); } - public class InvalidPacketException extends java.lang.Exception { + public final class InvalidPacketException extends java.lang.Exception { ctor public InvalidPacketException(int); method public int getError(); field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb @@ -6255,7 +6255,7 @@ package android.net { field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 } - public static class NetworkCapabilities.Builder { + public static final class NetworkCapabilities.Builder { ctor public NetworkCapabilities.Builder(); ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); diff --git a/api/test-current.txt b/api/test-current.txt index 0bd8a195edff..ba6feedc65ba 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -535,6 +535,7 @@ package android.app { method public void setWindowingMode(int); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTIVITY_TYPE_ASSISTANT = 4; // 0x4 + field public static final int ACTIVITY_TYPE_DREAM = 5; // 0x5 field public static final int ACTIVITY_TYPE_HOME = 2; // 0x2 field public static final int ACTIVITY_TYPE_RECENTS = 3; // 0x3 field public static final int ACTIVITY_TYPE_STANDARD = 1; // 0x1 @@ -1833,7 +1834,7 @@ package android.net { field public static final int TRANSPORT_TEST = 7; // 0x7 } - public static class NetworkCapabilities.Builder { + public static final class NetworkCapabilities.Builder { ctor public NetworkCapabilities.Builder(); ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bd3fee2d1fc7..21b56d3e337f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4974,10 +4974,8 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); - Class<? extends Activity> activityClass = null; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); if (r != null) { - activityClass = r.activity.getClass(); r.activity.mConfigChangeFlags |= configChanges; if (finishing) { r.activity.mFinished = true; @@ -5030,7 +5028,6 @@ public final class ActivityThread extends ClientTransactionHandler { synchronized (mResourcesManager) { mActivities.remove(token); } - StrictMode.decrementExpectedActivityCount(activityClass); return r; } @@ -5050,6 +5047,7 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance, reason); if (r != null) { + Class<? extends Activity> activityClass = r.activity.getClass(); cleanUpPendingRemoveWindows(r, finishing); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; @@ -5074,14 +5072,14 @@ public final class ActivityThread extends ClientTransactionHandler { } if (wtoken != null && r.mPendingRemoveWindow == null) { WindowManagerGlobal.getInstance().closeAll(wtoken, - r.activity.getClass().getName(), "Activity"); + activityClass.getName(), "Activity"); } else if (r.mPendingRemoveWindow != null) { // We're preserving only one window, others should be closed so app views // will be detached before the final tear down. It should be done now because // some components (e.g. WebView) rely on detach callbacks to perform receiver // unregister and other cleanup. WindowManagerGlobal.getInstance().closeAllExceptView(token, v, - r.activity.getClass().getName(), "Activity"); + activityClass.getName(), "Activity"); } r.activity.mDecor = null; } @@ -5093,18 +5091,23 @@ public final class ActivityThread extends ClientTransactionHandler { // about leaking windows, because that is a bug, so if they are // using this recreate facility then they get to live with leaks. WindowManagerGlobal.getInstance().closeAll(token, - r.activity.getClass().getName(), "Activity"); + activityClass.getName(), "Activity"); } // Mocked out contexts won't be participating in the normal // process lifecycle, but if we're running with a proper // ApplicationContext we need to have it tear down things // cleanly. - Context c = r.activity.getBaseContext(); - if (c instanceof ContextImpl) { - ((ContextImpl) c).scheduleFinalCleanup( - r.activity.getClass().getName(), "Activity"); + final ContextImpl impl = ContextImpl.getImpl(r.activity); + if (impl != null) { + impl.scheduleFinalCleanup(activityClass.getName(), "Activity"); } + + r.activity = null; + r.window = null; + r.hideForNow = false; + r.nextIdle = null; + StrictMode.decrementExpectedActivityCount(activityClass); } if (finishing) { try { @@ -5334,10 +5337,6 @@ public final class ActivityThread extends ClientTransactionHandler { handleDestroyActivity(r.token, false, configChanges, true, reason); - r.activity = null; - r.window = null; - r.hideForNow = false; - r.nextIdle = null; // Merge any pending results and pending intents; don't just replace them if (pendingResults != null) { if (r.pendingResults == null) { diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 948546b18473..78d3581a7012 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -48,8 +48,6 @@ interface INotificationManager void clearData(String pkg, int uid, boolean fromApp); void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @nullable ITransientNotificationCallback callback); void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); - // TODO(b/144152069): Remove this after assessing impact on dogfood. - void enqueueTextOrCustomToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId, boolean isCustom); void cancelToast(String pkg, IBinder token); void finishToken(String pkg, IBinder token); @@ -123,10 +121,14 @@ interface INotificationManager // INotificationListener method. @UnsupportedAppUsage StatusBarNotification[] getActiveNotifications(String callingPkg); + StatusBarNotification[] getActiveNotificationsWithAttribution(String callingPkg, + String callingAttributionTag); @UnsupportedAppUsage StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count, boolean includeSnoozed); + StatusBarNotification[] getHistoricalNotificationsWithAttribution(String callingPkg, + String callingAttributionTag, int count, boolean includeSnoozed); - NotificationHistory getNotificationHistory(String callingPkg); + NotificationHistory getNotificationHistory(String callingPkg, String callingAttributionTag); void registerListener(in INotificationListener listener, in ComponentName component, int userid); void unregisterListener(in INotificationListener listener, int userid); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index d650801f39ea..10f7835b3d69 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -801,6 +801,11 @@ public final class LoadedApk { makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths); String libraryPermittedPath = mDataDir; + if (mActivityThread == null) { + // In a zygote context where mActivityThread is null we can't access the app data dir + // and including this in libraryPermittedPath would cause SELinux denials. + libraryPermittedPath = ""; + } if (isBundledApp) { // For bundled apps, add the base directory of the app (e.g., diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 811b9c082be2..0e97e3fe06ce 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -29,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Build; @@ -841,7 +842,8 @@ public class NotificationManager { } /** - * Returns the notification channel settings for a given channel and conversation id. + * Returns the notification channel settings for a given channel and + * {@link ShortcutInfo#getId() conversation id}. * * <p>The channel must belong to your package, or to a package you are an approved notification * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 6b40890e17c7..37e07de9809a 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -141,6 +141,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public static final int ACTIVITY_TYPE_RECENTS = 3; /** Assistant activity type. */ public static final int ACTIVITY_TYPE_ASSISTANT = 4; + /** Dream activity type. */ + public static final int ACTIVITY_TYPE_DREAM = 5; /** @hide */ @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = { @@ -149,6 +151,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS, ACTIVITY_TYPE_ASSISTANT, + ACTIVITY_TYPE_DREAM, }) public @interface ActivityType {} @@ -746,9 +749,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu * @hide */ public boolean isAlwaysOnTop() { - return mWindowingMode == WINDOWING_MODE_PINNED || (mAlwaysOnTop == ALWAYS_ON_TOP_ON - && (mWindowingMode == WINDOWING_MODE_FREEFORM - || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW)); + if (mWindowingMode == WINDOWING_MODE_PINNED) return true; + if (mActivityType == ACTIVITY_TYPE_DREAM) return true; + if (mAlwaysOnTop != ALWAYS_ON_TOP_ON) return false; + return mWindowingMode == WINDOWING_MODE_FREEFORM + || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW; } /** @@ -798,7 +803,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu /** @hide */ public static boolean supportSplitScreenWindowingMode(int activityType) { - return activityType != ACTIVITY_TYPE_ASSISTANT; + return activityType != ACTIVITY_TYPE_ASSISTANT && activityType != ACTIVITY_TYPE_DREAM; } /** @hide */ @@ -823,6 +828,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu case ACTIVITY_TYPE_HOME: return "home"; case ACTIVITY_TYPE_RECENTS: return "recents"; case ACTIVITY_TYPE_ASSISTANT: return "assistant"; + case ACTIVITY_TYPE_DREAM: return "dream"; } return String.valueOf(applicationType); } diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java index e0674d75974a..fa62a02499e0 100644 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ b/core/java/android/bluetooth/BluetoothHearingAid.java @@ -379,6 +379,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + verifyDeviceNotNull(device, "setConnectionPolicy"); final IBluetoothHearingAid service = getService(); try { if (service != null && isEnabled() @@ -428,6 +429,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); + verifyDeviceNotNull(device, "getConnectionPolicy"); final IBluetoothHearingAid service = getService(); try { if (service != null && isEnabled() @@ -504,6 +506,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { if (VDBG) { log("getHiSyncId(" + device + ")"); } + verifyDeviceNotNull(device, "getConnectionPolicy"); final IBluetoothHearingAid service = getService(); try { if (service == null) { @@ -577,6 +580,13 @@ public final class BluetoothHearingAid implements BluetoothProfile { return false; } + private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { + if (device == null) { + Log.e(TAG, methodName + ": device param is null"); + throw new IllegalArgumentException("Device cannot be null"); + } + } + private boolean isValidDevice(BluetoothDevice device) { if (device == null) return false; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 95385ee9b79c..b1d6c830d3b7 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -11215,6 +11215,17 @@ public class Intent implements Parcelable, Cloneable { && hasWebURI(); } + private boolean isImageCaptureIntent() { + return (MediaStore.ACTION_IMAGE_CAPTURE.equals(mAction) + || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(mAction) + || MediaStore.ACTION_VIDEO_CAPTURE.equals(mAction)); + } + + /** @hide */ + public boolean isImplicitImageCaptureIntent() { + return mPackage == null && mComponent == null && isImageCaptureIntent(); + } + /** * @hide */ @@ -11241,9 +11252,7 @@ public class Intent implements Parcelable, Cloneable { } putParcelableArrayListExtra(EXTRA_STREAM, newStreams); } - } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) - || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action) - || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) { + } else if (isImageCaptureIntent()) { final Uri output = getParcelableExtra(MediaStore.EXTRA_OUTPUT); if (output != null) { putExtra(MediaStore.EXTRA_OUTPUT, maybeAddUserId(output, contentUserHint)); @@ -11349,9 +11358,7 @@ public class Intent implements Parcelable, Cloneable { } } catch (ClassCastException e) { } - } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) - || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action) - || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) { + } else if (isImageCaptureIntent()) { final Uri output; try { output = getParcelableExtra(MediaStore.EXTRA_OUTPUT); diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java index c31cb4e61b6e..9b3e8c9c137d 100644 --- a/core/java/android/inputmethodservice/InlineSuggestionSession.java +++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java @@ -166,9 +166,14 @@ class InlineSuggestionSession { } return; } - + // The IME doesn't have information about the virtual view id for the child views in the + // web view, so we are only comparing the parent view id here. This means that for cases + // where there are two input fields in the web view, they will have the same view id + // (although different virtual child id), and we will not be able to distinguish them. + final AutofillId imeClientFieldId = mClientAutofillIdSupplier.get(); if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get()) - || !fieldId.equalsIgnoreSession(mClientAutofillIdSupplier.get())) { + || imeClientFieldId == null + || fieldId.getViewId() != imeClientFieldId.getViewId()) { if (DEBUG) { Log.d(TAG, "handleOnInlineSuggestionsResponse() called on the wrong package/field " diff --git a/core/java/android/net/InvalidPacketException.java b/core/java/android/net/InvalidPacketException.java index b3b0f11a776b..1873d778c0f2 100644 --- a/core/java/android/net/InvalidPacketException.java +++ b/core/java/android/net/InvalidPacketException.java @@ -27,7 +27,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi -public class InvalidPacketException extends Exception { +public final class InvalidPacketException extends Exception { private final int mError; // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS. diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index fcfcebdf0862..af9414c56654 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -415,6 +415,20 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY); /** + * Capabilities that are allowed for test networks. This list must be set so that it is safe + * for an unprivileged user to create a network with these capabilities via shell. As such, + * it must never contain capabilities that are generally useful to the system, such as + * INTERNET, IMS, SUPL, etc. + */ + private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES = + (1 << NET_CAPABILITY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_NOT_VPN) + | (1 << NET_CAPABILITY_NOT_ROAMING) + | (1 << NET_CAPABILITY_NOT_CONGESTED) + | (1 << NET_CAPABILITY_NOT_SUSPENDED); + + /** * Adds the given capability to this {@code NetworkCapability} instance. * Note that when searching for a network to satisfy a request, all capabilities * requested must be satisfied. @@ -646,6 +660,21 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Test networks have strong restrictions on what capabilities they can have. Enforce these + * restrictions. + * @hide + */ + public void restrictCapabilitesForTestNetwork() { + final long originalCapabilities = mNetworkCapabilities; + final NetworkSpecifier originalSpecifier = mNetworkSpecifier; + clearAll(); + // Reset the transports to only contain TRANSPORT_TEST. + mTransportTypes = (1 << TRANSPORT_TEST); + mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES; + mNetworkSpecifier = originalSpecifier; + } + + /** * Representing the transport type. Apps should generally not care about transport. A * request for a fast internet connection could be satisfied by a number of different * transports. If any are specified here it will be satisfied a Network that matches @@ -2000,7 +2029,7 @@ public final class NetworkCapabilities implements Parcelable { */ @SystemApi @TestApi - public static class Builder { + public static final class Builder { private final NetworkCapabilities mCaps; /** diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 71f931da1a92..5cc73caa4f60 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -72,16 +72,16 @@ public class V4Signature { * V4 signature data. */ public static class SigningInfo { - public final byte[] v3Digest; // used to match with the corresponding APK + public final byte[] apkDigest; // used to match with the corresponding APK public final byte[] certificate; // ASN.1 DER form public final byte[] additionalData; // a free-form binary data blob public final byte[] publicKey; // ASN.1 DER, must match the certificate public final int signatureAlgorithmId; // see the APK v2 doc for the list public final byte[] signature; - SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData, + SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, byte[] publicKey, int signatureAlgorithmId, byte[] signature) { - this.v3Digest = v3Digest; + this.apkDigest = apkDigest; this.certificate = certificate; this.additionalData = additionalData; this.publicKey = publicKey; @@ -94,13 +94,13 @@ public class V4Signature { */ public static SigningInfo fromByteArray(byte[] bytes) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); - byte[] v3Digest = readBytes(buffer); + byte[] apkDigest = readBytes(buffer); byte[] certificate = readBytes(buffer); byte[] additionalData = readBytes(buffer); byte[] publicKey = readBytes(buffer); int signatureAlgorithmId = buffer.getInt(); byte[] signature = readBytes(buffer); - return new SigningInfo(v3Digest, certificate, additionalData, publicKey, + return new SigningInfo(apkDigest, certificate, additionalData, publicKey, signatureAlgorithmId, signature); } } @@ -150,7 +150,7 @@ public class V4Signature { final int size = 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( - signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize( + signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize( signingInfo.additionalData); ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(size); @@ -159,7 +159,7 @@ public class V4Signature { buffer.put(hashingInfo.log2BlockSize); writeBytes(buffer, hashingInfo.salt); writeBytes(buffer, hashingInfo.rawRootHash); - writeBytes(buffer, signingInfo.v3Digest); + writeBytes(buffer, signingInfo.apkDigest); writeBytes(buffer, signingInfo.certificate); writeBytes(buffer, signingInfo.additionalData); return buffer.array(); diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index bbc936d76e1c..99bdfd1fc103 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -194,4 +194,5 @@ interface IStorageManager { boolean needsCheckpoint() = 86; void abortChanges(in String message, boolean retry) = 87; void clearUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 88; + void fixupAppDir(in String path) = 89; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 1454aac66d21..a68cc3dfe0e4 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2470,6 +2470,36 @@ public class StorageManager { } } + /** + * Asks StorageManager to fixup the permissions of an application-private directory. + * + * On devices without sdcardfs, filesystem permissions aren't magically fixed up. This + * is problematic mostly in application-private directories, which are owned by the + * application itself; if another process with elevated permissions creates a file + * in these directories, the UID will be wrong, and the owning package won't be able + * to access the files. + * + * This API can be used to recursively fix up the permissions on the passed in path. + * The default platform user of this API is the DownloadProvider, which can download + * things in application-private directories on their behalf. + * + * This API doesn't require any special permissions, because it merely changes the + * permissions of a directory to what they should anyway be. + * + * @param path the path for which we should fix up the permissions + * + * @hide + */ + public void fixupAppDir(@NonNull File path) { + try { + mStorageManager.fixupAppDir(path.getCanonicalPath()); + } catch (IOException e) { + Log.e(TAG, "Failed to get canonical path for " + path.getPath(), e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** {@hide} */ private static void setCacheBehavior(File path, String name, boolean enabled) throws IOException { diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 7d070b1d9df6..f8265d6366ab 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -780,15 +780,16 @@ public class AlwaysOnHotwordDetector { audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION; } - int code = STATUS_ERROR; + int code; try { code = mModelManagementService.startRecognition( mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback, new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, recognitionExtra, null /* additional data */, audioCapabilities)); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in startRecognition!", e); + throw e.rethrowFromSystemServer(); } + if (code != STATUS_OK) { Slog.w(TAG, "startRecognition() failed with error code " + code); } @@ -796,12 +797,12 @@ public class AlwaysOnHotwordDetector { } private int stopRecognitionLocked() { - int code = STATUS_ERROR; + int code; try { code = mModelManagementService.stopRecognition(mKeyphraseMetadata.id, mInternalCallback); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in stopRecognition!", e); + throw e.rethrowFromSystemServer(); } if (code != STATUS_OK) { @@ -968,12 +969,12 @@ public class AlwaysOnHotwordDetector { } } - ModuleProperties dspModuleProperties = null; + ModuleProperties dspModuleProperties; try { dspModuleProperties = mModelManagementService.getDspModuleProperties(); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in getDspProperties!", e); + throw e.rethrowFromSystemServer(); } // No DSP available @@ -989,7 +990,7 @@ public class AlwaysOnHotwordDetector { mKeyphraseMetadata = mModelManagementService.getEnrolledKeyphraseMetadata( mText, mLocale.toLanguageTag()); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in internalUpdateEnrolledKeyphraseMetadata", e); + throw e.rethrowFromSystemServer(); } } } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index b54e4d9b876d..45d3465fdae8 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -35,6 +35,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.util.ArraySet; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceActionCheckCallback; @@ -47,6 +48,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; /** @@ -63,6 +65,8 @@ import java.util.Set; * separate process from this one. */ public class VoiceInteractionService extends Service { + static final String TAG = VoiceInteractionService.class.getSimpleName(); + /** * The {@link Intent} that must be declared as handled by the service. * To be supported, the service must also require the @@ -240,9 +244,22 @@ public class VoiceInteractionService extends Service { public void onReady() { mSystemService = IVoiceInteractionManagerService.Stub.asInterface( ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + Objects.requireNonNull(mSystemService); + try { + mSystemService.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + Log.wtf(TAG, "unable to link to death with system service"); + } mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); } + private IBinder.DeathRecipient mDeathRecipient = () -> { + Log.e(TAG, "system service binder died shutting down"); + Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( + VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this)); + }; + + private void onShutdownInternal() { onShutdown(); // Stop any active recognitions when shutting down. @@ -349,16 +366,24 @@ public class VoiceInteractionService extends Service { } private void safelyShutdownHotwordDetector() { - try { - synchronized (mLock) { - if (mHotwordDetector != null) { - mHotwordDetector.stopRecognition(); - mHotwordDetector.invalidate(); - mHotwordDetector = null; - } + synchronized (mLock) { + if (mHotwordDetector == null) { + return; + } + + try { + mHotwordDetector.stopRecognition(); + } catch (Exception ex) { + // Ignore. } - } catch (Exception ex) { - // Ignore. + + try { + mHotwordDetector.invalidate(); + } catch (Exception ex) { + // Ignore. + } + + mHotwordDetector = null; } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 04be71f2babc..346fe293d7ae 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -24,6 +24,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; import android.util.ArrayMap; @@ -117,7 +118,10 @@ public class ApkSignatureSchemeV2Verifier { return vSigner.certs; } - private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) + /** + * Same as above returns the full signer object, containing additional info e.g. digest. + */ + public static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk, verifyIntegrity); @@ -209,9 +213,11 @@ public class ApkSignatureSchemeV2Verifier { verityDigest, apk.length(), signatureInfo); } + byte[] digest = pickBestDigestForV4(contentDigests); + return new VerifiedSigner( signerCerts.toArray(new X509Certificate[signerCerts.size()][]), - verityRootHash); + verityRootHash, digest); } private static X509Certificate[] verifySigner( @@ -426,11 +432,14 @@ public class ApkSignatureSchemeV2Verifier { */ public static class VerifiedSigner { public final X509Certificate[][] certs; + public final byte[] verityRootHash; + public final byte[] digest; - public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) { + public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash, byte[] digest) { this.certs = certs; this.verityRootHash = verityRootHash; + this.digest = digest; } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 2437af26770b..4ab541b616ed 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -16,8 +16,6 @@ package android.util.apk; -import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; -import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; @@ -26,6 +24,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; import android.os.Build; @@ -213,24 +212,11 @@ public class ApkSignatureSchemeV3Verifier { verityDigest, apk.length(), signatureInfo); } - result.digest = pickBestV3DigestForV4(contentDigests); + result.digest = pickBestDigestForV4(contentDigests); return result; } - // Keep in sync with pickBestV3DigestForV4 in apksigner.V3SchemeVerifier. - private static byte[] pickBestV3DigestForV4(Map<Integer, byte[]> contentDigests) { - final int[] orderedContentDigestTypes = - {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256, - CONTENT_DIGEST_CHUNKED_SHA256}; - for (int contentDigestType : orderedContentDigestTypes) { - if (contentDigests.containsKey(contentDigestType)) { - return contentDigests.get(contentDigestType); - } - } - return null; - } - private static VerifiedSigner verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java index 8c240d99f590..d40efce0b3b3 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java @@ -145,7 +145,7 @@ public class ApkSignatureSchemeV4Verifier { "Public key mismatch between certificate and signature record"); } - return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest); + return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.apkDigest); } /** @@ -155,11 +155,11 @@ public class ApkSignatureSchemeV4Verifier { */ public static class VerifiedSigner { public final Certificate[] certs; - public byte[] v3Digest; + public byte[] apkDigest; - public VerifiedSigner(Certificate[] certs, byte[] v3Digest) { + public VerifiedSigner(Certificate[] certs, byte[] apkDigest) { this.certs = certs; - this.v3Digest = v3Digest; + this.apkDigest = apkDigest; } } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index c1cee48cc663..ab8f80d3d1a5 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -184,27 +184,45 @@ public class ApkSignatureVerifier { Signature[] signerSigs = convertToSignatures(signerCerts); if (verifyFull) { - // v4 is an add-on and requires v3 signature to validate against its certificates - ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming = - ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); - Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs}; - Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); + byte[] nonstreamingDigest = null; + Certificate[][] nonstreamingCerts = null; + + try { + // v4 is an add-on and requires v2 or v3 signature to validate against its + // certificate and digest + ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer = + ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); + nonstreamingDigest = v3Signer.digest; + nonstreamingCerts = new Certificate[][]{v3Signer.certs}; + } catch (SignatureNotFoundException e) { + try { + ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer = + ApkSignatureSchemeV2Verifier.verify(apkPath, false); + nonstreamingDigest = v2Signer.digest; + nonstreamingCerts = v2Signer.certs; + } catch (SignatureNotFoundException ee) { + throw new SecurityException( + "V4 verification failed to collect V2/V3 certificates from : " + + apkPath, ee); + } + } + Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); if (nonstreamingSigs.length != signerSigs.length) { throw new SecurityException( - "Invalid number of certificates: " + nonstreaming.certs.length); + "Invalid number of certificates: " + nonstreamingSigs.length); } for (int i = 0, size = signerSigs.length; i < size; ++i) { if (!nonstreamingSigs[i].equals(signerSigs[i])) { - throw new SecurityException("V4 signature certificate does not match V3"); + throw new SecurityException( + "V4 signature certificate does not match V2/V3"); } } - // TODO(b/151240006): add support for v2 digest and make it mandatory. - if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest, - nonstreaming.digest, vSigner.v3Digest.length)) { - throw new SecurityException("V3 digest in V4 signature does not match V3"); + if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest, + vSigner.apkDigest.length)) { + throw new SecurityException("APK digest in V4 signature does not match V2/V3"); } } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 4fe8515143a0..2a4b65d23e64 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -421,6 +421,10 @@ final class ApkSigningBlockUtils { static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3; + private static final int[] V4_CONTENT_DIGEST_ALGORITHMS = + {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256, + CONTENT_DIGEST_CHUNKED_SHA256}; + static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); @@ -572,6 +576,21 @@ final class ApkSigningBlockUtils { } /** + * Returns the best digest from the map of available digests. + * similarly to compareContentDigestAlgorithm. + * + * Keep in sync with pickBestDigestForV4 in apksigner's ApkSigningBlockUtils. + */ + static byte[] pickBestDigestForV4(Map<Integer, byte[]> contentDigests) { + for (int algo : V4_CONTENT_DIGEST_ALGORITHMS) { + if (contentDigests.containsKey(algo)) { + return contentDigests.get(algo); + } + } + return null; + } + + /** * Returns new byte buffer whose content is a shared subsequence of this buffer's content * between the specified start (inclusive) and end (exclusive) positions. As opposed to * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 43f80f1490ad..e6bd84391fef 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -17,6 +17,7 @@ package android.view; import static android.view.InsetsState.ITYPE_IME; +import static android.view.InsetsState.toInternalType; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.ime; @@ -26,8 +27,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONT import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; import android.annotation.IntDef; @@ -38,11 +37,9 @@ import android.graphics.Rect; import android.os.CancellationSignal; import android.os.Handler; import android.os.RemoteException; -import android.renderscript.Sampler.Value; import android.util.ArraySet; import android.util.Log; import android.util.Pair; -import android.util.Property; import android.util.SparseArray; import android.view.InsetsSourceConsumer.ShowResult; import android.view.InsetsState.InternalInsetsType; @@ -467,7 +464,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!localStateChanged && mLastDispachedState.equals(state)) { return false; } - mState.set(state); + updateState(state); mLastDispachedState.set(state, true /* copySources */); applyLocalVisibilityOverride(); if (localStateChanged) { @@ -479,6 +476,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return true; } + private void updateState(InsetsState newState) { + mState.setDisplayFrame(newState.getDisplayFrame()); + for (int i = newState.getSourcesCount() - 1; i >= 0; i--) { + InsetsSource source = newState.sourceAt(i); + getSourceConsumer(source.getType()).updateSource(source); + } + for (int i = mState.getSourcesCount() - 1; i >= 0; i--) { + InsetsSource source = mState.sourceAt(i); + if (newState.peekSource(source.getType()) == null) { + mState.removeSource(source.getType()); + } + } + } + /** * @see InsetsState#calculateInsets */ @@ -858,8 +869,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation control.cancel(); } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { - if (mRunningAnimations.get(i).runner == control) { + RunningAnimation runningAnimation = mRunningAnimations.get(i); + if (runningAnimation.runner == control) { mRunningAnimations.remove(i); + ArraySet<Integer> types = toInternalType(control.getTypes()); + for (int j = types.size() - 1; j >= 0; j--) { + if (getSourceConsumer(types.valueAt(j)).notifyAnimationFinished()) { + mViewRoot.notifyInsetsChanged(); + } + } break; } } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 332573449e18..f36621c0a49e 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -16,11 +16,13 @@ package android.view; +import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.toPublicType; import android.annotation.IntDef; import android.annotation.Nullable; +import android.graphics.Rect; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; @@ -64,6 +66,8 @@ public class InsetsSourceConsumer { private final Supplier<Transaction> mTransactionSupplier; private @Nullable InsetsSourceControl mSourceControl; private boolean mHasWindowFocus; + private Rect mPendingFrame; + private Rect mPendingVisibleFrame; public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { @@ -215,6 +219,38 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } + void updateSource(InsetsSource newSource) { + InsetsSource source = mState.peekSource(mType); + if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE + || source.getFrame().equals(newSource.getFrame())) { + mState.addSource(newSource); + return; + } + + // Frame is changing while animating. Keep note of the new frame but keep existing frame + // until animaition is finished. + newSource = new InsetsSource(newSource); + mPendingFrame = new Rect(newSource.getFrame()); + mPendingVisibleFrame = newSource.getVisibleFrame() != null + ? new Rect(newSource.getVisibleFrame()) + : null; + newSource.setFrame(source.getFrame()); + newSource.setVisibleFrame(source.getVisibleFrame()); + mState.addSource(newSource); + } + + boolean notifyAnimationFinished() { + if (mPendingFrame != null) { + InsetsSource source = mState.getSource(mType); + source.setFrame(mPendingFrame); + source.setVisibleFrame(mPendingVisibleFrame); + mPendingFrame = null; + mPendingVisibleFrame = null; + return true; + } + return false; + } + /** * Sets requested visibility from the client, regardless of whether we are able to control it at * the moment. diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 59fc6e9b5ede..c89e0c9fc60e 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -176,6 +176,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall boolean mUseAlpha = false; float mSurfaceAlpha = 1f; boolean mClipSurfaceToBounds; + int mBackgroundColor = Color.BLACK; @UnsupportedAppUsage boolean mHaveFrame = false; @@ -828,6 +829,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } + private Transaction updateBackgroundColor(Transaction t) { + final float[] colorComponents = new float[] { Color.red(mBackgroundColor) / 255.f, + Color.green(mBackgroundColor) / 255.f, Color.blue(mBackgroundColor) / 255.f }; + t.setColor(mBackgroundControl, colorComponents); + return t; + } private void releaseSurfaces() { mSurfaceAlpha = 1f; @@ -1000,6 +1007,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } updateBackgroundVisibility(mTmpTransaction); + updateBackgroundColor(mTmpTransaction); if (mUseAlpha) { mTmpTransaction.setAlpha(mSurfaceControl, alpha); mSurfaceAlpha = alpha; @@ -1399,10 +1407,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall return; } - final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f, - Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f }; - - mTmpTransaction.setColor(mBackgroundControl, colorComponents).apply(); + mBackgroundColor = bgColor; + updateBackgroundColor(mTmpTransaction).apply(); } @UnsupportedAppUsage diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 4aeea10eee68..62dd192a6d67 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -405,6 +405,13 @@ public class Editor { // The actual zoom value may changes based on this initial zoom value. private float mInitialZoom = 1f; + // For calculating the line change slops while moving cursor/selection. + // The slop max/min value include line height and the slop on the upper/lower line. + private static final int LINE_CHANGE_SLOP_MAX_DP = 45; + private static final int LINE_CHANGE_SLOP_MIN_DP = 12; + private int mLineChangeSlopMax; + private int mLineChangeSlopMin; + Editor(TextView textView) { mTextView = textView; // Synchronize the filter list, which places the undo input filter at the end. @@ -430,6 +437,14 @@ public class Editor { logCursor("Editor", "New magnifier is %s.", mNewMagnifierEnabled ? "enabled" : "disabled"); } + + mLineChangeSlopMax = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MAX_DP, + mTextView.getContext().getResources().getDisplayMetrics()); + mLineChangeSlopMin = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MIN_DP, + mTextView.getContext().getResources().getDisplayMetrics()); + } @VisibleForTesting @@ -6018,7 +6033,14 @@ public class Editor { } } - private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { + @VisibleForTesting + public void setLineChangeSlopMinMaxForTesting(final int min, final int max) { + mLineChangeSlopMin = min; + mLineChangeSlopMax = max; + } + + @VisibleForTesting + public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { final int trueLine = mTextView.getLineAtCoordinate(y); if (layout == null || prevLine > layout.getLineCount() || layout.getLineCount() <= 0 || prevLine < 0) { @@ -6031,28 +6053,21 @@ public class Editor { return trueLine; } + final int lineHeight = layout.getLineBottom(prevLine) - layout.getLineTop(prevLine); + int slop = (int)(LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS + * (layout.getLineBottom(trueLine) - layout.getLineTop(trueLine))); + slop = Math.max(mLineChangeSlopMin, + Math.min(mLineChangeSlopMax, lineHeight + slop)) - lineHeight; + slop = Math.max(0, slop); + final float verticalOffset = mTextView.viewportToContentVerticalOffset(); - final int lineCount = layout.getLineCount(); - final float slop = mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS; - - final float firstLineTop = layout.getLineTop(0) + verticalOffset; - final float prevLineTop = layout.getLineTop(prevLine) + verticalOffset; - final float yTopBound = Math.max(prevLineTop - slop, firstLineTop + slop); - - final float lastLineBottom = layout.getLineBottom(lineCount - 1) + verticalOffset; - final float prevLineBottom = layout.getLineBottom(prevLine) + verticalOffset; - final float yBottomBound = Math.min(prevLineBottom + slop, lastLineBottom - slop); - - // Determine if we've moved lines based on y position and previous line. - int currLine; - if (y <= yTopBound) { - currLine = Math.max(prevLine - 1, 0); - } else if (y >= yBottomBound) { - currLine = Math.min(prevLine + 1, lineCount - 1); - } else { - currLine = prevLine; + if (trueLine > prevLine && y >= layout.getLineBottom(prevLine) + slop + verticalOffset) { + return trueLine; + } + if (trueLine < prevLine && y <= layout.getLineTop(prevLine) - slop + verticalOffset) { + return trueLine; } - return currLine; + return prevLine; } /** diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 8943da4a2fc0..4f14539dd976 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -144,9 +144,6 @@ public class Toast { @Nullable private CharSequence mText; - // TODO(b/144152069): Remove this after assessing impact on dogfood. - private boolean mIsCustomToast; - /** * Construct an empty Toast object. You must call {@link #setView} before you * can call {@link #show}. @@ -214,8 +211,7 @@ public class Toast { service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); } } else { - service.enqueueTextOrCustomToast(pkg, mToken, tn, mDuration, displayId, - mIsCustomToast); + service.enqueueToast(pkg, mToken, tn, mDuration, displayId); } } catch (RemoteException e) { // Empty @@ -253,7 +249,6 @@ public class Toast { */ @Deprecated public void setView(View view) { - mIsCustomToast = true; mNextView = view; } diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index c83cab77dd13..31527e8dbe5d 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -227,7 +227,7 @@ public class ResolverDrawerLayout extends ViewGroup { } final int oldCollapsibleHeight = mCollapsibleHeight; - mCollapsibleHeight = Math.max(mCollapsibleHeight, getMaxCollapsedHeight()); + mCollapsibleHeight = Math.min(mCollapsibleHeight, getMaxCollapsedHeight()); if (updateCollapseOffset(oldCollapsibleHeight, !isDragging())) { return; diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index ed2c5b2b4930..ab57e3dcdc53 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2665,4 +2665,8 @@ enum PageId { // Open: Settings > Sound > Do Not Disturb > People > Messages // OS: R DND_MESSAGES = 1839; + + // Open: Settings > Sound > Do Not Disturb > Apps > <Choose App> + // OS: R + DND_APPS_BYPASSING = 1840; } diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 2ef0c927cc61..88f9fc2199e5 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -702,7 +702,7 @@ please see themes_device_defaults.xml. </style> <style name="Theme.Dream"> - <item name="windowBackground">@null</item> + <item name="windowBackground">@color/black</item> <item name="windowDisablePreview">true</item> </style> diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 34a1016f0ae9..efdb51dcdfa9 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -638,6 +639,32 @@ public class InsetsControllerTest { }); } + @Test + public void testFrameUpdateDuringAnimation() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + + mController.onControlsChanged(createSingletonControl(ITYPE_IME)); + + // Pretend IME is calling + mController.show(ime(), true /* fromIme */); + + InsetsState copy = new InsetsState(mController.getState(), true /* copySources */); + copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3); + copy.getSource(ITYPE_IME).setVisibleFrame(new Rect(4, 5, 6, 7)); + mController.onStateChanged(copy); + assertNotEquals(new Rect(0, 1, 2, 3), + mController.getState().getSource(ITYPE_IME).getFrame()); + assertNotEquals(new Rect(4, 5, 6, 7), + mController.getState().getSource(ITYPE_IME).getVisibleFrame()); + mController.cancelExistingAnimation(); + assertEquals(new Rect(0, 1, 2, 3), + mController.getState().getSource(ITYPE_IME).getFrame()); + assertEquals(new Rect(4, 5, 6, 7), + mController.getState().getSource(ITYPE_IME).getVisibleFrame()); + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + private void waitUntilNextFrame() throws Exception { final CountDownLatch latch = new CountDownLatch(1); Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT, diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index ba27fac05a99..eae1bbc930d4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -15,11 +15,12 @@ */ package android.view.contentcapture; +import static org.mockito.Mockito.mock; import static org.testng.Assert.assertThrows; +import android.content.ContentCaptureOptions; import android.content.Context; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -37,16 +38,20 @@ public class ContentCaptureManagerTest { @Mock private Context mMockContext; - private ContentCaptureManager mManager; - - @Before - public void before() { - mManager = new ContentCaptureManager(mMockContext, /* service= */ null, - /* options= */ null); + @Test + public void testConstructor_invalidParametersThrowsException() { + assertThrows(NullPointerException.class, + () -> new ContentCaptureManager(mMockContext, /* service= */ null, /* options= */ + null)); } @Test - public void testRemoveData_invalid() { - assertThrows(NullPointerException.class, () -> mManager.removeData(null)); + public void testRemoveData_invalidParametersThrowsException() { + final IContentCaptureManager mockService = mock(IContentCaptureManager.class); + final ContentCaptureOptions options = new ContentCaptureOptions(null); + final ContentCaptureManager manager = + new ContentCaptureManager(mMockContext, mockService, options); + + assertThrows(NullPointerException.class, () -> manager.removeData(null)); } } diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index f81964c9cdf9..1b5ce8fd4ce5 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -16,6 +16,7 @@ package android.widget; +import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.dragOnText; import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; @@ -37,6 +38,9 @@ import android.app.Activity; import android.app.Instrumentation; import android.graphics.Rect; import android.text.Layout; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.AbsoluteSizeSpan; import android.util.ArraySet; import android.util.Log; import android.view.InputDevice; @@ -489,6 +493,38 @@ public class EditorCursorDragTest { } @Test + public void testLineChangeSlop() throws Throwable { + TextView tv = mActivity.findViewById(R.id.textview); + Spannable s = new SpannableString("a\nb\nc"); + s.setSpan(new AbsoluteSizeSpan(10), 2, 4, SPAN_INCLUSIVE_EXCLUSIVE); + s.setSpan(new AbsoluteSizeSpan(32), 4, 5, SPAN_INCLUSIVE_EXCLUSIVE); + mInstrumentation.runOnMainSync(() -> tv.setText(s)); + + Layout layout = tv.getLayout(); + Editor editor = tv.getEditorForTesting(); + final float verticalOffset = tv.getExtendedPaddingTop(); + editor.setLineChangeSlopMinMaxForTesting(30, 65); + // Hit top part of upper line, jump to upper line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 5 + verticalOffset)) + .isEqualTo(0); + // Hit bottom part of upper line, stay at current line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 40 + verticalOffset)) + .isEqualTo(1); + // Hit current line, stay at current line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 70 + verticalOffset)) + .isEqualTo(1); + // Hit top part of lower line, stay at current line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 85 + verticalOffset)) + .isEqualTo(1); + // Hit bottom part of lower line, jump to lower line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 110 + verticalOffset)) + .isEqualTo(2); + // Hit lower line of lower line, jump to target line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 0, 110 + verticalOffset)) + .isEqualTo(2); + } + + @Test public void testCursorDrag_snapDistance() throws Throwable { String text = "line1: This is the 1st line: A\n" + "line2: This is the 2nd line: B\n" diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml index 5c89da05a68c..98f5824011ae 100644 --- a/data/etc/hiddenapi-package-whitelist.xml +++ b/data/etc/hiddenapi-package-whitelist.xml @@ -61,7 +61,6 @@ Do NOT include any apps that are updatable via Play Store! <hidden-api-whitelisted-app package="com.android.terminal" /> <hidden-api-whitelisted-app package="com.android.wallpaper" /> <hidden-api-whitelisted-app package="jp.co.omronsoft.openwnn" /> - <!-- STOPSHIP: Remove this when fixing all @hide usage for tethering.--> - <hidden-api-whitelisted-app package="com.android.networkstack.tethering" /> + <!-- TODO: Remove NetworkStack whitelisting --> <hidden-api-whitelisted-app package="com.android.networkstack" /> </config> diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 383202b20550..fffdd683f22c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4579,6 +4579,7 @@ public class AudioManager { * {@hide} */ @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setWiredDeviceConnectionState(int type, int state, String address, String name) { final IAudioService service = getService(); try { diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index c0461bc598ed..1d70a0dbc57c 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -46,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -3048,16 +3049,47 @@ final public class MediaCodec { * @param block The linear block object * @param offset The byte offset into the input buffer at which the data starts. * @param size The number of bytes of valid input data. - * @param cryptoInfo Metadata describing the structure of the encrypted input sample. - * may be null for non-encrypted content. * @return this object * @throws IllegalStateException if a buffer is already set */ public @NonNull QueueRequest setLinearBlock( @NonNull LinearBlock block, int offset, + int size) { + if (!isAccessible()) { + throw new IllegalStateException("The request is stale"); + } + if (mLinearBlock != null || mHardwareBuffer != null) { + throw new IllegalStateException("Cannot set block twice"); + } + mLinearBlock = block; + mOffset = offset; + mSize = size; + mCryptoInfo = null; + return this; + } + + /** + * Set an encrypted linear block to this queue request. Exactly one buffer must be + * set for a queue request before calling {@link #queue}. It is possible + * to use the same {@link LinearBlock} object for multiple queue + * requests. The behavior is undefined if the range of the buffer + * overlaps for multiple requests, or the application writes into the + * region being processed by the codec. + * + * @param block The linear block object + * @param offset The byte offset into the input buffer at which the data starts. + * @param size The number of bytes of valid input data. + * @param cryptoInfo Metadata describing the structure of the encrypted input sample. + * @return this object + * @throws IllegalStateException if a buffer is already set + */ + public @NonNull QueueRequest setEncryptedLinearBlock( + @NonNull LinearBlock block, + int offset, int size, - @Nullable MediaCodec.CryptoInfo cryptoInfo) { + @NonNull MediaCodec.CryptoInfo cryptoInfo) { + Objects.requireNonNull(cryptoInfo); if (!isAccessible()) { throw new IllegalStateException("The request is stale"); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index d17f242d5d63..a1fba4a018e2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -174,7 +174,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { @Override public boolean isEnabled(BluetoothDevice device) { - if (mService == null) { + if (mService == null || device == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; @@ -182,7 +182,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { @Override public int getConnectionPolicy(BluetoothDevice device) { - if (mService == null) { + if (mService == null || device == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); @@ -191,7 +191,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { boolean isEnabled = false; - if (mService == null) { + if (mService == null || device == null) { return false; } if (enabled) { @@ -213,7 +213,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { } public long getHiSyncId(BluetoothDevice device) { - if (mService == null) { + if (mService == null || device == null) { return BluetoothHearingAid.HI_SYNC_ID_INVALID; } return mService.getHiSyncId(device); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index b1300a97a324..922caeb0a817 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -66,6 +66,7 @@ public class LocalMediaManager implements BluetoothCallback { private LocalBluetoothManager mLocalBluetoothManager; private InfoMediaManager mInfoMediaManager; private String mPackageName; + private MediaDevice mOnTransferBluetoothDevice; @VisibleForTesting List<MediaDevice> mMediaDevices = new ArrayList<>(); @@ -143,7 +144,7 @@ public class LocalMediaManager implements BluetoothCallback { final CachedBluetoothDevice cachedDevice = ((BluetoothMediaDevice) device).getCachedDevice(); if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { - device.setState(MediaDeviceState.STATE_CONNECTING); + mOnTransferBluetoothDevice = connectDevice; cachedDevice.connect(); return; } @@ -389,6 +390,10 @@ public class LocalMediaManager implements BluetoothCallback { mCurrentConnectedDevice = infoMediaDevice != null ? infoMediaDevice : updateCurrentConnectedDevice(); dispatchDeviceListUpdate(); + if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { + connectDevice(mOnTransferBluetoothDevice); + mOnTransferBluetoothDevice = null; + } } private List<MediaDevice> buildDisconnectedBluetoothDevice() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 6b3a97f8c8de..4c61ef504090 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -107,8 +108,8 @@ public class LocalMediaManagerTest { when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); - mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1, - TEST_PACKAGE_NAME); + mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1, + TEST_PACKAGE_NAME)); mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2, TEST_PACKAGE_NAME); mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, @@ -565,6 +566,34 @@ public class LocalMediaManagerTest { } @Test + public void onDeviceListAdded_transferToDisconnectedBluetooth_verifyConnectDevice() { + final List<MediaDevice> devices = new ArrayList<>(); + final MediaDevice currentDevice = mock(MediaDevice.class); + final MediaDevice device = mock(BluetoothMediaDevice.class); + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + mLocalMediaManager.mMediaDevices.add(device); + mLocalMediaManager.mMediaDevices.add(currentDevice); + + when(device.getId()).thenReturn(TEST_DEVICE_ID_1); + when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID); + when(((BluetoothMediaDevice) device).getCachedDevice()).thenReturn(cachedDevice); + when(cachedDevice.isConnected()).thenReturn(false); + when(cachedDevice.isBusy()).thenReturn(false); + + mLocalMediaManager.registerCallback(mCallback); + mLocalMediaManager.connectDevice(device); + + verify(cachedDevice).connect(); + when(device.isConnected()).thenReturn(true); + mLocalMediaManager.mCurrentConnectedDevice = currentDevice; + devices.add(mInfoMediaDevice1); + devices.add(currentDevice); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); + + verify(mInfoMediaDevice1).connect(); + } + + @Test public void onRequestFailed_shouldDispatchOnRequestFailed() { mLocalMediaManager.registerCallback(mCallback); diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml index a06f434d068c..65b04fd8fd99 100644 --- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml +++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml @@ -19,6 +19,7 @@ android:id="@+id/bubble_overflow_container" android:layout_width="match_parent" android:layout_height="match_parent" + android:paddingTop="@dimen/bubble_overflow_padding" android:orientation="vertical" android:layout_gravity="center_horizontal"> diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml new file mode 100644 index 000000000000..d67c81d67ada --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/bubble_overflow_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <com.android.systemui.bubbles.BadgedImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/bubble_view" + android:layout_gravity="center" + android:layout_width="@dimen/individual_bubble_size" + android:layout_height="@dimen/individual_bubble_size"/> + + <TextView + android:id="@+id/bubble_view_name" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2" + android:textColor="?android:attr/textColorSecondary" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:maxLines="1" + android:layout_gravity="center" + android:gravity="center"/> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_header.xml index 9c2d244cb8ec..de9ef218083b 100644 --- a/packages/SystemUI/res/layout/keyguard_media_header.xml +++ b/packages/SystemUI/res/layout/keyguard_media_header.xml @@ -52,7 +52,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:gravity="center_horizontal|fill_vertical" + android:gravity="center" android:padding="16dp" > <ImageView @@ -67,7 +67,7 @@ <LinearLayout android:orientation="vertical" android:layout_width="0dp" - android:layout_height="@dimen/qs_media_album_size" + android:layout_height="wrap_content" android:layout_weight="1" > <LinearLayout diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml index 9ef8c1dbbb95..fe8557bfcce4 100644 --- a/packages/SystemUI/res/layout/qs_media_panel.xml +++ b/packages/SystemUI/res/layout/qs_media_panel.xml @@ -56,7 +56,7 @@ <LinearLayout android:orientation="vertical" android:layout_width="0dp" - android:layout_height="@dimen/qs_media_album_size" + android:layout_height="wrap_content" android:layout_weight="1" > <LinearLayout diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e45cbecd3aa1..432cd749abbd 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1150,8 +1150,8 @@ <dimen name="bubble_overflow_height">380dp</dimen> <!-- Bubble overflow padding when there are no bubbles --> <dimen name="bubble_overflow_empty_state_padding">16dp</dimen> - <!-- Margin of overflow bubbles --> - <dimen name="bubble_overflow_margin">16dp</dimen> + <!-- Padding of container for overflow bubbles --> + <dimen name="bubble_overflow_padding">5dp</dimen> <!-- Height of the triangle that points to the expanded bubble --> <dimen name="bubble_pointer_height">4dp</dimen> <!-- Width of the triangle that points to the expanded bubble --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 07bd3a0567e6..136935080824 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -73,14 +73,19 @@ public class QuickStepContract { public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1 << 9; // The search feature is disabled (either by SUW/SysUI/device policy) public static final int SYSUI_STATE_SEARCH_DISABLED = 1 << 10; - // The notification panel is expanded and interactive (either locked or unlocked), and the - // quick settings is not expanded + // The notification panel is expanded and interactive (either locked or unlocked), and quick + // settings is expanded. public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11; // Winscope tracing is enabled public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12; // The Assistant gesture should be constrained. It is up to the launcher implementation to // decide how to constrain it public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13; + // The bubble stack is expanded. This means that the home gesture should be ignored, since a + // swipe up is an attempt to close the bubble stack, but that the back gesture should remain + // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble + // stack. + public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14; @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -96,7 +101,8 @@ public class QuickStepContract { SYSUI_STATE_HOME_DISABLED, SYSUI_STATE_SEARCH_DISABLED, SYSUI_STATE_TRACING_ENABLED, - SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED + SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, + SYSUI_STATE_BUBBLES_EXPANDED }) public @interface SystemUiStateFlags {} @@ -118,6 +124,7 @@ public class QuickStepContract { str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : ""); str.add((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0 ? "asst_gesture_constrain" : ""); + str.add((flags & SYSUI_STATE_BUBBLES_EXPANDED) != 0 ? "bubbles_expanded" : ""); return str.toString(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java index b0017269ff7e..4fcacc276ac1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java @@ -114,7 +114,9 @@ public class KeyguardMediaPlayer { throw new IllegalStateException("cannot update controls, views not bound"); } if (mediaMetadata == null) { - throw new IllegalArgumentException("media metadata was null"); + mMediaNotifView.setVisibility(View.GONE); + Log.d(TAG, "media metadata was null"); + return; } mMediaNotifView.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c9ce8a10cca7..01c2faa62403 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -74,6 +74,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.bubbles.dagger.BubbleModule; import com.android.systemui.dump.DumpManager; +import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PinnedStackListenerForwarder; @@ -174,6 +175,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private IStatusBarService mBarService; + private SysUiState mSysUiState; // Used for determining view rect for touch interaction private Rect mTempRect = new Rect(); @@ -290,11 +292,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotifPipeline notifPipeline, FeatureFlags featureFlags, DumpManager dumpManager, - FloatingContentCoordinator floatingContentCoordinator) { + FloatingContentCoordinator floatingContentCoordinator, + SysUiState sysUiState) { this(context, notificationShadeWindowController, statusBarStateController, shadeController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager, entryManager, - notifPipeline, featureFlags, dumpManager, floatingContentCoordinator); + notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState); } /** @@ -315,7 +318,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotifPipeline notifPipeline, FeatureFlags featureFlags, DumpManager dumpManager, - FloatingContentCoordinator floatingContentCoordinator) { + FloatingContentCoordinator floatingContentCoordinator, + SysUiState sysUiState) { dumpManager.registerDumpable(TAG, this); mContext = context; mShadeController = shadeController; @@ -340,6 +344,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }); configurationController.addCallback(this /* configurationListener */); + mSysUiState = sysUiState; mBubbleData = data; mBubbleData.setListener(mBubbleDataListener); @@ -593,7 +598,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private void ensureStackViewCreated() { if (mStackView == null) { mStackView = new BubbleStackView( - mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator); + mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, + mSysUiState); ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView(); int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble)); int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim. @@ -957,9 +963,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi String key = orderedKeys[i]; NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key); rankingMap.getRanking(key, mTmpRanking); - if (mBubbleData.hasBubbleWithKey(key) && !mTmpRanking.canBubble()) { + boolean isActiveBubble = mBubbleData.hasBubbleWithKey(key); + if (isActiveBubble && !mTmpRanking.canBubble()) { mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED); - } else if (entry != null && mTmpRanking.isBubble()) { + } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { entry.setFlagBubble(true); onEntryUpdated(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 077ffd3729e5..8bc35f4058e9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -233,6 +233,7 @@ public class BubbleData { Bubble b = mOverflowBubbles.get(i); if (b.getKey().equals(entry.getKey())) { moveOverflowBubbleToPending(b); + b.setEntry(entry); return b; } } @@ -240,6 +241,7 @@ public class BubbleData { for (int i = 0; i < mPendingBubbles.size(); i++) { Bubble b = mPendingBubbles.get(i); if (b.getKey().equals(entry.getKey())) { + b.setEntry(entry); return b; } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index 7636c6712e41..b65198595095 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -21,14 +21,20 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.app.Activity; +import android.app.Notification; +import android.app.Person; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Bundle; +import android.os.Parcelable; +import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import android.widget.TextView; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -67,13 +73,22 @@ public class BubbleOverflowActivity extends Activity { mEmptyState = findViewById(R.id.bubble_overflow_empty_state); mRecyclerView = findViewById(R.id.bubble_overflow_recycler); + + Resources res = getResources(); + final int columns = res.getInteger(R.integer.bubbles_overflow_columns); mRecyclerView.setLayoutManager( - new GridLayoutManager(getApplicationContext(), - getResources().getInteger(R.integer.bubbles_overflow_columns))); + new GridLayoutManager(getApplicationContext(), columns)); + + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + final int viewWidth = displayMetrics.widthPixels / columns; + + final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow); + final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns); + final int viewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height) / rows; - int bubbleMargin = getResources().getDimensionPixelSize(R.dimen.bubble_overflow_margin); mAdapter = new BubbleOverflowAdapter(mOverflowBubbles, - mBubbleController::promoteBubbleFromOverflow, bubbleMargin); + mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight); mRecyclerView.setAdapter(mAdapter); onDataChanged(mBubbleController.getOverflowBubbles()); mBubbleController.setOverflowCallback(() -> { @@ -139,39 +154,48 @@ public class BubbleOverflowActivity extends Activity { class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> { private Consumer<Bubble> mPromoteBubbleFromOverflow; private List<Bubble> mBubbles; - private int mBubbleMargin; + private int mWidth; + private int mHeight; - public BubbleOverflowAdapter(List<Bubble> list, Consumer<Bubble> promoteBubble, - int bubbleMargin) { + public BubbleOverflowAdapter(List<Bubble> list, Consumer<Bubble> promoteBubble, int width, + int height) { mBubbles = list; mPromoteBubbleFromOverflow = promoteBubble; - mBubbleMargin = bubbleMargin; + mWidth = width; + mHeight = height; } @Override public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext()) - .inflate(R.layout.bubble_view, parent, false); + LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.bubble_overflow_view, parent, false); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - params.setMargins(mBubbleMargin, mBubbleMargin, mBubbleMargin, mBubbleMargin); - view.setLayoutParams(params); - return new ViewHolder(view); + LinearLayout.LayoutParams.WRAP_CONTENT); + params.width = mWidth; + params.height = mHeight; + overflowView.setLayoutParams(params); + return new ViewHolder(overflowView); } @Override public void onBindViewHolder(ViewHolder vh, int index) { - Bubble bubble = mBubbles.get(index); + Bubble b = mBubbles.get(index); - vh.mBadgedImageView.update(bubble); - vh.mBadgedImageView.setOnClickListener(view -> { - mBubbles.remove(bubble); + vh.iconView.update(b); + vh.iconView.setOnClickListener(view -> { + mBubbles.remove(b); notifyDataSetChanged(); - mPromoteBubbleFromOverflow.accept(bubble); + mPromoteBubbleFromOverflow.accept(b); }); + + Bubble.FlyoutMessage message = b.getFlyoutMessage(); + if (message != null && message.senderName != null) { + vh.textView.setText(message.senderName); + } else { + vh.textView.setText(b.getAppName()); + } } @Override @@ -180,11 +204,13 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V } public static class ViewHolder extends RecyclerView.ViewHolder { - public BadgedImageView mBadgedImageView; + public BadgedImageView iconView; + public TextView textView; - public ViewHolder(BadgedImageView v) { + public ViewHolder(LinearLayout v) { super(v); - mBadgedImageView = v; + iconView = v.findViewById(R.id.bubble_view); + textView = v.findViewById(R.id.bubble_view_name); } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 541c8cf19943..7191a203ea8c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -80,6 +80,8 @@ import com.android.systemui.R; import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; +import com.android.systemui.model.SysUiState; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; @@ -241,6 +243,7 @@ public class BubbleStackView extends FrameLayout { private BubbleTouchHandler mTouchHandler; private BubbleController.BubbleExpandListener mExpandListener; + private SysUiState mSysUiState; private boolean mViewUpdatedRequested = false; private boolean mIsExpansionAnimating = false; @@ -437,7 +440,8 @@ public class BubbleStackView extends FrameLayout { public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, - FloatingContentCoordinator floatingContentCoordinator) { + FloatingContentCoordinator floatingContentCoordinator, + SysUiState sysUiState) { super(context); mBubbleData = data; @@ -445,6 +449,8 @@ public class BubbleStackView extends FrameLayout { mTouchHandler = new BubbleTouchHandler(this, data, context); setOnTouchListener(mTouchHandler); + mSysUiState = sysUiState; + Resources res = getResources(); mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); @@ -1055,6 +1061,11 @@ public class BubbleStackView extends FrameLayout { if (shouldExpand == mIsExpanded) { return; } + + mSysUiState + .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand) + .commitUpdate(mContext.getDisplayId()); + if (mIsExpanded) { animateCollapse(); logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index 27c9e9895324..e84e932c9e61 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -21,6 +21,7 @@ import android.content.Context; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubbleData; import com.android.systemui.dump.DumpManager; +import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -62,7 +63,8 @@ public interface BubbleModule { NotifPipeline notifPipeline, FeatureFlags featureFlags, DumpManager dumpManager, - FloatingContentCoordinator floatingContentCoordinator) { + FloatingContentCoordinator floatingContentCoordinator, + SysUiState sysUiState) { return new BubbleController( context, notificationShadeWindowController, @@ -79,6 +81,7 @@ public interface BubbleModule { notifPipeline, featureFlags, dumpManager, - floatingContentCoordinator); + floatingContentCoordinator, + sysUiState); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 09d7d26e4dfe..b3299916356c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -490,6 +490,9 @@ public class DozeTriggers implements DozeMachine.Part { public void onPowerSaveChanged(boolean active) { if (mDozeHost.isPowerSaveActive()) { mMachine.requestState(DozeMachine.State.DOZE); + } else if (mMachine.getState() == DozeMachine.State.DOZE + && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) { + mMachine.requestState(DozeMachine.State.DOZE_AOD); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 73539f9380e6..6514ca44cf98 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1849,7 +1849,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, .alpha(1) .translationX(0) .translationY(0) - .setDuration(300) + .setDuration(450) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setUpdateListener(animation -> { float animatedValue = animation.getAnimatedFraction(); @@ -1878,7 +1878,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, .alpha(0) .translationX(mGlobalActionsLayout.getAnimationOffsetX()) .translationY(mGlobalActionsLayout.getAnimationOffsetY()) - .setDuration(300) + .setDuration(550) .withEndAction(this::completeDismiss) .setInterpolator(new LogAccelerateInterpolator()) .setUpdateListener(animation -> { diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 902b5785c30a..6ce5e7cf506c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -346,6 +346,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper .crop(tx, mLeash, destinationBounds) + .resetScale(tx, mLeash, destinationBounds) .round(tx, mLeash, mInPip); scheduleFinishResizePip(tx, destinationBounds, TRANSITION_DIRECTION_NONE, null); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java index 014f3b51b4dc..1a01cfedb82e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -208,7 +208,8 @@ public class PipResizeGestureHandler { final Rect currentPipBounds = mMotionHelper.getBounds(); mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, - mMinSize.y, mMaxSize, true, true)); + mMinSize.y, mMaxSize, true, + mLastDownBounds.width() > mLastDownBounds.height())); mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds); mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, null); diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java index 3cdc01d95f58..dea8c32dc88d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java +++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java @@ -38,7 +38,7 @@ public class CurrentUserObservable { @Override protected void onInactive() { super.onInactive(); - mTracker.startTracking(); + mTracker.stopTracking(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 3879c164a84c..1aa78311a989 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -25,6 +25,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; +import android.app.ActivityTaskManager; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -129,6 +130,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, private int mDividerInsets; private final Display mDefaultDisplay; + private boolean mSupportSplitScreenMultiWindow; private int mDividerSize; private int mTouchElevation; @@ -282,6 +284,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, final DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + mSupportSplitScreenMultiWindow = + ActivityTaskManager.supportsSplitScreenMultiWindow(mContext); } @Override @@ -354,6 +358,11 @@ public class DividerView extends FrameLayout implements OnTouchListener, @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!mSupportSplitScreenMultiWindow) { + super.onLayout(changed, left, top, right, bottom); + return; + } + if (mFirstLayout) { // Wait for first layout so that the ViewRootImpl surface has been created. initializeSurfaceState(); @@ -1085,6 +1094,13 @@ public class DividerView extends FrameLayout implements OnTouchListener, crop.offsetTo(-(otherTaskRect.left - otherRect.left), -(otherTaskRect.top - otherRect.top)); t.setWindowCrop(mTiles.mSecondarySurface, crop); + // Reposition home and recents surfaces or they would be positioned relatively to its + // parent (split-screen secondary task) position. + for (int i = mTiles.mHomeAndRecentsSurfaces.size() - 1; i >= 0; --i) { + t.setPosition(mTiles.mHomeAndRecentsSurfaces.get(i), + mTiles.mHomeBounds.left - otherTaskRect.left, + mTiles.mHomeBounds.top - otherTaskRect.top); + } final SurfaceControl dividerCtrl = getWindowSurfaceControl(); if (dividerCtrl != null) { if (isHorizontalDivision()) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java index c4089e5dd070..6cb7f4ff7204 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java @@ -26,12 +26,15 @@ import static android.window.WindowOrganizer.TaskOrganizer; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; +import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; import android.view.Display; -import android.window.ITaskOrganizer; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.ITaskOrganizer; + +import java.util.ArrayList; class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { private static final String TAG = "SplitScreenTaskOrganizer"; @@ -43,6 +46,8 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { SurfaceControl mSecondarySurface; SurfaceControl mPrimaryDim; SurfaceControl mSecondaryDim; + ArrayList<SurfaceControl> mHomeAndRecentsSurfaces = new ArrayList<>(); + Rect mHomeBounds = new Rect(); final Divider mDivider; SplitScreenTaskOrganizer(Divider divider) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index 8724e490a558..6ed7afe152df 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -29,9 +29,9 @@ import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; import android.view.Display; +import android.view.WindowManagerGlobal; import android.window.IWindowContainer; import android.window.WindowContainerTransaction; -import android.view.WindowManagerGlobal; import android.window.WindowOrganizer; import com.android.internal.annotations.GuardedBy; @@ -157,6 +157,7 @@ public class WindowManagerProxy { for (int i = homeStacks.size() - 1; i >= 0; --i) { wct.setBounds(homeStacks.get(i), homeBounds); } + layout.mTiles.mHomeBounds.set(homeBounds); return isHomeResizable; } @@ -180,13 +181,17 @@ public class WindowManagerProxy { if (rootTasks.isEmpty()) { return false; } + tiles.mHomeAndRecentsSurfaces.clear(); for (int i = rootTasks.size() - 1; i >= 0; --i) { - if (rootTasks.get(i).configuration.windowConfiguration.getWindowingMode() + final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i); + if (isHomeOrRecentTask(rootTask)) { + tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash()); + } + if (rootTask.configuration.windowConfiguration.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { continue; } - wct.reparent(rootTasks.get(i).token, tiles.mSecondary.token, - true /* onTop */); + wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); } boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct); WindowOrganizer.applyTransaction(wct); @@ -213,6 +218,7 @@ public class WindowManagerProxy { // Set launch root first so that any task created after getChildContainers and // before reparent (pretty unlikely) are put into fullscreen. TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null); + tiles.mHomeAndRecentsSurfaces.clear(); // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished // plus specific APIs to clean this up. List<ActivityManager.RunningTaskInfo> primaryChildren = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 8945f360f7b8..a3faa80485d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -67,32 +67,9 @@ class NotificationShadeDepthController @Inject constructor( private var updateScheduled: Boolean = false private var shadeExpansion = 0f @VisibleForTesting - var shadeSpring = SpringAnimation(this, object : - FloatPropertyCompat<NotificationShadeDepthController>("shadeBlurRadius") { - override fun setValue(rect: NotificationShadeDepthController?, value: Float) { - shadeBlurRadius = value.toInt() - } - - override fun getValue(rect: NotificationShadeDepthController?): Float { - return shadeBlurRadius.toFloat() - } - }) - private val zoomInterpolator = Interpolators.ACCELERATE_DECELERATE - - /** - * Radius that we're animating to. - */ - private var pendingShadeBlurRadius = -1 - - /** - * Shade blur radius on the current frame. - */ - private var shadeBlurRadius = 0 - set(value) { - if (field == value) return - field = value - scheduleUpdate() - } + var shadeSpring = DepthAnimation() + @VisibleForTesting + var globalActionsSpring = DepthAnimation() /** * Blur radius of the wake-up animation on this frame. @@ -103,7 +80,6 @@ class NotificationShadeDepthController @Inject constructor( field = value scheduleUpdate() } - private var globalDialogVisibility = 0f /** * Callback that updates the window blur value and is called only once per frame. @@ -111,12 +87,9 @@ class NotificationShadeDepthController @Inject constructor( private val updateBlurCallback = Choreographer.FrameCallback { updateScheduled = false - val blur = max(shadeBlurRadius, - max(wakeAndUnlockBlurRadius, blurUtils.blurRadiusOfRatio(globalDialogVisibility))) + val blur = max(max(shadeSpring.radius, wakeAndUnlockBlurRadius), globalActionsSpring.radius) blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur) - val rawZoom = max(blurUtils.ratioOfBlurRadius(blur), globalDialogVisibility) - wallpaperManager.setWallpaperZoomOut(root.windowToken, - zoomInterpolator.getInterpolation(rawZoom)) + wallpaperManager.setWallpaperZoomOut(root.windowToken, blurUtils.ratioOfBlurRadius(blur)) notificationShadeWindowController.setBackgroundBlurRadius(blur) } @@ -163,8 +136,9 @@ class NotificationShadeDepthController @Inject constructor( } override fun onDozingChanged(isDozing: Boolean) { - if (isDozing && shadeSpring.isRunning) { - shadeSpring.skipToEnd() + if (isDozing) { + shadeSpring.finishIfRunning() + globalActionsSpring.finishIfRunning() } } } @@ -174,10 +148,6 @@ class NotificationShadeDepthController @Inject constructor( if (WAKE_UP_ANIMATION_ENABLED) { keyguardStateController.addCallback(keyguardStateCallback) } - shadeSpring.spring = SpringForce(0.0f) - shadeSpring.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY - shadeSpring.spring.stiffness = SpringForce.STIFFNESS_LOW - shadeSpring.addEndListener { _, _, _, _ -> pendingShadeBlurRadius = -1 } statusBarStateController.addCallback(statusBarStateCallback) } @@ -198,11 +168,7 @@ class NotificationShadeDepthController @Inject constructor( newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) } - if (pendingShadeBlurRadius == newBlur) { - return - } - pendingShadeBlurRadius = newBlur - shadeSpring.animateToFinalPosition(newBlur.toFloat()) + shadeSpring.animateTo(newBlur) } private fun scheduleUpdate(viewToBlur: View? = null) { @@ -215,19 +181,72 @@ class NotificationShadeDepthController @Inject constructor( } fun updateGlobalDialogVisibility(visibility: Float, dialogView: View) { - if (visibility == globalDialogVisibility) { - return - } - globalDialogVisibility = visibility - scheduleUpdate(dialogView) + globalActionsSpring.animateTo(blurUtils.blurRadiusOfRatio(visibility), dialogView) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { IndentingPrintWriter(pw, " ").let { it.println("StatusBarWindowBlurController:") it.increaseIndent() - it.println("shadeBlurRadius: $shadeBlurRadius") + it.println("shadeRadius: ${shadeSpring.radius}") + it.println("globalActionsRadius: ${globalActionsSpring.radius}") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") } } + + /** + * Animation helper that smoothly animates the depth using a spring and deals with frame + * invalidation. + */ + inner class DepthAnimation() { + /** + * Blur radius visible on the UI, in pixels. + */ + var radius = 0 + private set + + /** + * Radius that we're animating to. + */ + private var pendingRadius = -1 + + /** + * View on {@link Surface} that wants depth. + */ + private var view: View? = null + + private var springAnimation = SpringAnimation(this, object : + FloatPropertyCompat<DepthAnimation>("blurRadius") { + override fun setValue(rect: DepthAnimation?, value: Float) { + radius = value.toInt() + scheduleUpdate(view) + } + + override fun getValue(rect: DepthAnimation?): Float { + return radius.toFloat() + } + }) + + init { + springAnimation.spring = SpringForce(0.0f) + springAnimation.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY + springAnimation.spring.stiffness = SpringForce.STIFFNESS_MEDIUM + springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 } + } + + fun animateTo(newRadius: Int, viewToBlur: View? = null) { + if (pendingRadius == newRadius && view == viewToBlur) { + return + } + view = viewToBlur + pendingRadius = newRadius + springAnimation.animateToFinalPosition(newRadius.toFloat()) + } + + fun finishIfRunning() { + if (springAnimation.isRunning) { + springAnimation.skipToEnd() + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index c6d84ff79bde..d2f781d2e19c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -651,7 +651,7 @@ public class NotificationEntryManager implements */ public void updateNotifications(String reason) { reapplyFilterAndSort(reason); - if (mPresenter != null) { + if (mPresenter != null && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mPresenter.updateNotificationViews(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt index 0437877d8330..cf670bd5a424 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt @@ -142,9 +142,11 @@ class NotifViewManager @Inject constructor( // To attach rows we can use _this one weird trick_: if the intended view to add does not // have a parent, then simply add it (and its children). entries.forEach { entry -> - val listItem = rowRegistry.requireView(entry) + // TODO: We should eventually map GroupEntry's themselves to views so that we don't + // depend on representativeEntry here which may actually be null in the future + val listItem = rowRegistry.requireView(entry.representativeEntry!!) - if (listItem.view.parent != null) { + if (listItem.view.parent == null) { listContainer.addListItem(listItem) stabilityManager.notifyViewAddition(listItem.view) } @@ -153,7 +155,8 @@ class NotifViewManager @Inject constructor( for ((idx, childEntry) in entry.children.withIndex()) { val childListItem = rowRegistry.requireView(childEntry) // Child hasn't been added yet. add it! - if (!listItem.notificationChildren.contains(childListItem)) { + if (listItem.notificationChildren == null || + !listItem.notificationChildren.contains(childListItem)) { // TODO: old code here just Log.wtf()'d here. This might wreak havoc if (childListItem.view.parent != null) { throw IllegalStateException("trying to add a notification child that " + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 7019b5b42cf4..808e1b38eb43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -108,6 +108,12 @@ public final class NotificationEntry extends ListEntry { /** If this notification was filtered out, then the filter that did the filtering. */ @Nullable NotifFilter mExcludingFilter; + /** + * The NotifFilter, if any, that was active on this notification during the previous run of + * the list builder. + */ + @Nullable NotifFilter mPreviousExcludingFilter; + /** If this was a group child that was promoted to the top level, then who did the promoting. */ @Nullable NotifPromoter mNotifPromoter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index f7d6cef92b8f..19f7cefe76a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -316,6 +316,7 @@ public class ShadeListBuilder implements Dumpable { // Step 7: Lock in our group structure and log anything that's changed since the last run mPipelineState.incrementTo(STATE_FINALIZING); + logFilterChanges(); logParentingChanges(); freeEmptyGroups(); @@ -363,6 +364,9 @@ public class ShadeListBuilder implements Dumpable { entry.setPreviousParent(entry.getParent()); entry.setParent(null); + entry.mPreviousExcludingFilter = entry.mExcludingFilter; + entry.mExcludingFilter = null; + if (entry.mFirstAddedIteration == -1) { entry.mFirstAddedIteration = mIterationCount; } @@ -371,8 +375,10 @@ public class ShadeListBuilder implements Dumpable { mNotifList.clear(); } - private void filterNotifs(Collection<? extends ListEntry> entries, - List<ListEntry> out, List<NotifFilter> filters) { + private void filterNotifs( + Collection<? extends ListEntry> entries, + List<ListEntry> out, + List<NotifFilter> filters) { final long now = mSystemClock.uptimeMillis(); for (ListEntry entry : entries) { if (entry instanceof GroupEntry) { @@ -585,8 +591,9 @@ public class ShadeListBuilder implements Dumpable { * filtered out during any of the filtering steps. */ private void annulAddition(ListEntry entry) { - entry.setSection(-1); - entry.mNotifSection = null; + // TODO: We should null out the entry's section and promoter here. However, if we do that, + // future runs will think that the section changed. We need a mPreviousNotifSection, + // similar to what we do for parents. entry.setParent(null); if (entry.mFirstAddedIteration == mIterationCount) { entry.mFirstAddedIteration = -1; @@ -615,6 +622,17 @@ public class ShadeListBuilder implements Dumpable { mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty()); } + private void logFilterChanges() { + for (NotificationEntry entry : mAllEntries) { + if (entry.mExcludingFilter != entry.mPreviousExcludingFilter) { + mLogger.logFilterChanged( + entry.getKey(), + entry.mPreviousExcludingFilter, + entry.mExcludingFilter); + } + } + } + private void logParentingChanges() { for (NotificationEntry entry : mAllEntries) { if (entry.getParent() != entry.getPreviousParent()) { @@ -680,21 +698,8 @@ public class ShadeListBuilder implements Dumpable { }; private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) { - NotifFilter filter = findRejectingFilter(entry, now, filters); - - if (filter != entry.mExcludingFilter) { - mLogger.logFilterChanged( - entry.getKey(), - entry.mExcludingFilter != null ? entry.mExcludingFilter.getName() : null, - filter != null ? filter.getName() : null); - - // Note that groups and summaries can also be filtered out later if they're part of a - // malformed group. We currently don't have a great way to track that beyond parenting - // change logs. Consider adding something similar to mExcludingFilter for them. - entry.mExcludingFilter = filter; - } - - return filter != null; + entry.mExcludingFilter = findRejectingFilter(entry, now, filters); + return entry.mExcludingFilter != null; } @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java new file mode 100644 index 000000000000..261ae079a2c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import android.content.pm.UserInfo; +import android.util.SparseArray; + +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +import javax.inject.Inject; + +/** + * A coordinator that filters out notifications for other users + * + * The NotifCollection contains the notifs for ALL users, so we need to remove any notifications + * that have been posted specifically to other users. Note that some system notifications are not + * posted to any particular user, and so must be shown to everyone. + * + * TODO: The NotificationLockscreenUserManager currently maintains the list of active user profiles. + * We should spin that off into a standalone section at some point. + */ +public class HideNotifsForOtherUsersCoordinator implements Coordinator { + private final NotificationLockscreenUserManager mLockscreenUserManager; + + @Inject + public HideNotifsForOtherUsersCoordinator( + NotificationLockscreenUserManager lockscreenUserManager) { + mLockscreenUserManager = lockscreenUserManager; + } + + @Override + public void attach(NotifPipeline pipeline) { + pipeline.addPreGroupFilter(mFilter); + mLockscreenUserManager.addUserChangedListener(mUserChangedListener); + } + + private final NotifFilter mFilter = new NotifFilter("NotCurrentUserFilter") { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return !mLockscreenUserManager + .isCurrentProfile(entry.getSbn().getUser().getIdentifier()); + } + }; + + private final UserChangedListener mUserChangedListener = new UserChangedListener() { + @Override + public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) { + mFilter.invalidateList(); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index aaf71f58cb5c..b7738569ad3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -95,11 +95,6 @@ public class KeyguardCoordinator implements Coordinator { public boolean shouldFilterOut(NotificationEntry entry, long now) { final StatusBarNotification sbn = entry.getSbn(); - // FILTER OUT the notification when the notification isn't for the current profile - if (!mLockscreenUserManager.isCurrentProfile(sbn.getUserId())) { - return true; - } - // FILTER OUT the notification when the keyguard is showing and... if (mKeyguardStateController.isShowing()) { // ... user settings or the device policy manager doesn't allow lockscreen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 98104f84f30e..03c0ae6fde50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -49,15 +49,17 @@ public class NotifCoordinators implements Dumpable { public NotifCoordinators( DumpManager dumpManager, FeatureFlags featureFlags, - HeadsUpCoordinator headsUpCoordinator, + HideNotifsForOtherUsersCoordinator hideNotifsForOtherUsersCoordinator, KeyguardCoordinator keyguardCoordinator, RankingCoordinator rankingCoordinator, ForegroundCoordinator foregroundCoordinator, DeviceProvisionedCoordinator deviceProvisionedCoordinator, BubbleCoordinator bubbleCoordinator, + HeadsUpCoordinator headsUpCoordinator, PreparationCoordinator preparationCoordinator) { dumpManager.registerDumpable(TAG, this); mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); + mCoordinators.add(hideNotifsForOtherUsersCoordinator); mCoordinators.add(keyguardCoordinator); mCoordinators.add(rankingCoordinator); mCoordinators.add(foregroundCoordinator); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 7e9e76096873..e9cbf32ee052 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -45,7 +45,8 @@ public class RankingCoordinator implements Coordinator { public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); - pipeline.addPreGroupFilter(mNotifFilter); + pipeline.addPreGroupFilter(mSuspendedFilter); + pipeline.addPreGroupFilter(mDozingFilter); } /** @@ -53,33 +54,30 @@ public class RankingCoordinator implements Coordinator { * NotifListBuilder invalidates the notification list each time the ranking is updated, * so we don't need to explicitly invalidate this filter on ranking update. */ - private final NotifFilter mNotifFilter = new NotifFilter(TAG) { + private final NotifFilter mSuspendedFilter = new NotifFilter("IsSuspendedFilter") { @Override public boolean shouldFilterOut(NotificationEntry entry, long now) { - // App suspended from Ranking - if (entry.getRanking().isSuspended()) { - return true; - } + return entry.getRanking().isSuspended(); + } + }; + private final NotifFilter mDozingFilter = new NotifFilter("IsDozingFilter") { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { // Dozing + DND Settings from Ranking object if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) { return true; } - if (!mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList()) { - return true; - } - - return false; + return !mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList(); } }; - private final StatusBarStateController.StateListener mStatusBarStateCallback = new StatusBarStateController.StateListener() { @Override public void onDozingChanged(boolean isDozing) { - mNotifFilter.invalidateList(); + mDozingFilter.invalidateList(); } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index 763547ce1638..e946cf16b3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -23,6 +23,7 @@ import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import javax.inject.Inject class ShadeListBuilderLogger @Inject constructor( @@ -126,13 +127,13 @@ class ShadeListBuilderLogger @Inject constructor( fun logFilterChanged( key: String, - prevFilter: String?, - newFilter: String? + prevFilter: NotifFilter?, + newFilter: NotifFilter? ) { buffer.log(TAG, INFO, { str1 = key - str2 = prevFilter - str3 = newFilter + str2 = prevFilter?.name + str3 = newFilter?.name }, { "Filter changed for $str1: $str2 -> $str3" }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index be8af82f8baf..823b18660bed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -6478,7 +6478,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private boolean hasActiveNotifications() { if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - return mNotifPipeline.getShadeList().isEmpty(); + return !mNotifPipeline.getShadeList().isEmpty(); } else { return mEntryManager.hasActiveNotifications(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index cf9d43eeff73..d70484e9cf41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; - import android.annotation.IntDef; import android.content.Context; import android.content.res.ColorStateList; @@ -30,241 +28,98 @@ import android.os.Trace; import android.provider.Settings; import android.text.TextUtils; import android.util.AttributeSet; -import android.view.ViewTreeObserver; -import android.view.accessibility.AccessibilityNodeInfo; +import android.util.SparseArray; +import android.view.ViewTreeObserver.OnPreDrawListener; import com.android.internal.graphics.ColorUtils; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.KeyguardAffordanceView; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.phone.ScrimController.ScrimVisibility; -import com.android.systemui.statusbar.policy.AccessibilityController; -import com.android.systemui.statusbar.policy.KeyguardStateController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import javax.inject.Inject; -import javax.inject.Named; - /** * Manages the different states and animations of the unlock icon. */ -public class LockIcon extends KeyguardAffordanceView implements - ViewTreeObserver.OnPreDrawListener { - - private static final int STATE_LOCKED = 0; - private static final int STATE_LOCK_OPEN = 1; - private static final int STATE_SCANNING_FACE = 2; - private static final int STATE_BIOMETRICS_ERROR = 3; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final AccessibilityController mAccessibilityController; - private final KeyguardStateController mKeyguardStateController; - private final KeyguardBypassController mBypassController; - private final NotificationWakeUpCoordinator mWakeUpCoordinator; - private final HeadsUpManagerPhone mHeadsUpManager; - - private int mLastState = 0; - private boolean mForceUpdate; - private boolean mTransientBiometricsError; - private boolean mIsFaceUnlockState; - private boolean mSimLocked; - private int mDensity; +public class LockIcon extends KeyguardAffordanceView { + + static final int STATE_LOCKED = 0; + static final int STATE_LOCK_OPEN = 1; + static final int STATE_SCANNING_FACE = 2; + static final int STATE_BIOMETRICS_ERROR = 3; + private float mDozeAmount; + private int mIconColor; + private StateProvider mStateProvider; + private int mOldState; private boolean mPulsing; private boolean mDozing; - private boolean mDocked; - private boolean mBlockUpdates; - private int mIconColor; - private float mDozeAmount; - private boolean mBouncerShowingScrimmed; - private boolean mWakeAndUnlockRunning; - private boolean mKeyguardShowing; - private boolean mShowingLaunchAffordance; private boolean mKeyguardJustShown; - private boolean mUpdatePending; - private boolean mBouncerPreHideAnimation; - private int mStatusBarState = StatusBarState.SHADE; - - private final KeyguardStateController.Callback mKeyguardMonitorCallback = - new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - boolean force = false; - boolean wasShowing = mKeyguardShowing; - mKeyguardShowing = mKeyguardStateController.isShowing(); - if (!wasShowing && mKeyguardShowing && mBlockUpdates) { - mBlockUpdates = false; - force = true; - } - if (!wasShowing && mKeyguardShowing) { - mKeyguardJustShown = true; - } - update(force); - } - - @Override - public void onKeyguardFadingAwayChanged() { - if (!mKeyguardStateController.isKeyguardFadingAway()) { - mBouncerPreHideAnimation = false; - if (mBlockUpdates) { - mBlockUpdates = false; - update(true /* force */); - } - } - } - - @Override - public void onUnlockedChanged() { - update(); - } - }; - - @Inject - public LockIcon(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, - AccessibilityController accessibilityController, - KeyguardBypassController bypassController, - NotificationWakeUpCoordinator wakeUpCoordinator, - KeyguardStateController keyguardStateController, - HeadsUpManagerPhone headsUpManager) { - super(context, attrs); - mContext = context; - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); - mAccessibilityController = accessibilityController; - mBypassController = bypassController; - mWakeUpCoordinator = wakeUpCoordinator; - mKeyguardStateController = keyguardStateController; - mHeadsUpManager = headsUpManager; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mKeyguardStateController.addCallback(mKeyguardMonitorCallback); - mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure(); - update(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mKeyguardStateController.removeCallback(mKeyguardMonitorCallback); - } + private final SparseArray<Drawable> mDrawableCache = new SparseArray<>(); - /** - * If we're currently presenting an authentication error message. - */ - public void setTransientBiometricsError(boolean transientBiometricsError) { - mTransientBiometricsError = transientBiometricsError; - update(); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - final int density = newConfig.densityDpi; - if (density != mDensity) { - mDensity = density; - update(); - } - } - - public void update() { - update(false /* force */); - } - - public void update(boolean force) { - if (force) { - mForceUpdate = true; - } - if (!mUpdatePending) { - mUpdatePending = true; - getViewTreeObserver().addOnPreDrawListener(this); - } - } + private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getViewTreeObserver().removeOnPreDrawListener(this); - @Override - public boolean onPreDraw() { - mUpdatePending = false; - getViewTreeObserver().removeOnPreDrawListener(this); - - int state = getState(); - int lastState = mLastState; - boolean keyguardJustShown = mKeyguardJustShown; - mIsFaceUnlockState = state == STATE_SCANNING_FACE; - mLastState = state; - mKeyguardJustShown = false; - - boolean shouldUpdate = lastState != state || mForceUpdate; - if (mBlockUpdates && canBlockUpdates()) { - shouldUpdate = false; - } - if (shouldUpdate) { - mForceUpdate = false; - @LockAnimIndex final int lockAnimIndex = getAnimationIndexForTransition(lastState, - state, mPulsing, mDozing, keyguardJustShown); - boolean isAnim = lockAnimIndex != -1; - int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(state); - - Drawable icon = mContext.getDrawable(iconRes); - final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable - ? (AnimatedVectorDrawable) icon - : null; + int newState = mStateProvider.getState(); + Drawable icon = getIcon(newState); setImageDrawable(icon, false); - if (mIsFaceUnlockState) { - announceForAccessibility(getContext().getString( + + if (newState == STATE_SCANNING_FACE) { + announceForAccessibility(getResources().getString( R.string.accessibility_scanning_face)); } - if (animation != null && isAnim) { + if (icon instanceof AnimatedVectorDrawable) { + final AnimatedVectorDrawable animation = (AnimatedVectorDrawable) icon; animation.forceAnimationOnUI(); animation.clearAnimationCallbacks(); - animation.registerAnimationCallback(new Animatable2.AnimationCallback() { - @Override - public void onAnimationEnd(Drawable drawable) { - if (getDrawable() == animation && state == getState() - && doesAnimationLoop(lockAnimIndex)) { - animation.start(); - } else { - Trace.endAsyncSection("LockIcon#Animation", state); - } - } - }); - Trace.beginAsyncSection("LockIcon#Animation", state); + animation.registerAnimationCallback( + new Animatable2.AnimationCallback() { + @Override + public void onAnimationEnd(Drawable drawable) { + if (getDrawable() == animation + && newState == mStateProvider.getState() + && newState == STATE_SCANNING_FACE) { + animation.start(); + } else { + Trace.endAsyncSection("LockIcon#Animation", newState); + } + } + }); + Trace.beginAsyncSection("LockIcon#Animation", newState); animation.start(); } + + return true; } - updateDarkTint(); + }; - updateIconVisibility(); - updateClickability(); + public LockIcon(Context context, AttributeSet attrs) { + super(context, attrs); + } - return true; + void setStateProvider(StateProvider stateProvider) { + mStateProvider = stateProvider; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDrawableCache.clear(); } /** * Update the icon visibility * @return true if the visibility changed */ - boolean updateIconVisibility() { - boolean onAodNotPulsingOrDocked = mDozing && (!mPulsing || mDocked); - boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning - || mShowingLaunchAffordance; - if (mBypassController.getBypassEnabled() && !mBouncerShowingScrimmed) { - if ((mHeadsUpManager.isHeadsUpGoingAway() || mHeadsUpManager.hasPinnedHeadsUp() - || mStatusBarState == StatusBarState.KEYGUARD) - && !mWakeUpCoordinator.getNotificationsFullyHidden()) { - invisible = true; - } - } - boolean wasInvisible = getVisibility() == INVISIBLE; - if (invisible != wasInvisible) { - setVisibility(invisible ? INVISIBLE : VISIBLE); + boolean updateIconVisibility(boolean visible) { + boolean wasVisible = getVisibility() == VISIBLE; + if (visible != wasVisible) { + setVisibility(visible ? VISIBLE : INVISIBLE); animate().cancel(); - if (!invisible) { + if (visible) { setScaleX(0); setScaleY(0); animate() @@ -280,49 +135,47 @@ public class LockIcon extends KeyguardAffordanceView implements return false; } - private boolean canBlockUpdates() { - return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway(); + void update(int oldState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { + mOldState = oldState; + mPulsing = pulsing; + mDozing = dozing; + mKeyguardJustShown = keyguardJustShown; + + getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); } - private void updateClickability() { - if (mAccessibilityController == null) { - return; - } - boolean canLock = mKeyguardStateController.isMethodSecure() - && mKeyguardStateController.canDismissLockScreen(); - boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled(); - setClickable(clickToUnlock); - setLongClickable(canLock && !clickToUnlock); - setFocusable(mAccessibilityController.isAccessibilityEnabled()); + void setDozeAmount(float dozeAmount) { + mDozeAmount = dozeAmount; + updateDarkTint(); } - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - boolean fingerprintRunning = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); - // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong - // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to - // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the - // check of whether non-strong biometric is allowed - boolean unlockingAllowed = mKeyguardUpdateMonitor - .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */); - if (fingerprintRunning && unlockingAllowed) { - AccessibilityNodeInfo.AccessibilityAction unlock - = new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfo.ACTION_CLICK, - getContext().getString(R.string.accessibility_unlock_without_fingerprint)); - info.addAction(unlock); - info.setHintText(getContext().getString( - R.string.accessibility_waiting_for_fingerprint)); - } else if (mIsFaceUnlockState) { - //Avoid 'button' to be spoken for scanning face - info.setClassName(LockIcon.class.getName()); - info.setContentDescription(getContext().getString( - R.string.accessibility_scanning_face)); + void onThemeChange(int iconColor) { + mDrawableCache.clear(); + mIconColor = iconColor; + updateDarkTint(); + } + + private void updateDarkTint() { + int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount); + setImageTintList(ColorStateList.valueOf(color)); + } + + private Drawable getIcon(int newState) { + @LockAnimIndex final int lockAnimIndex = + getAnimationIndexForTransition(mOldState, newState, mPulsing, mDozing, + mKeyguardJustShown); + + boolean isAnim = lockAnimIndex != -1; + int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(newState); + + if (!mDrawableCache.contains(iconRes)) { + mDrawableCache.put(iconRes, getResources().getDrawable(iconRes)); } + + return mDrawableCache.get(iconRes); } - private int getIconForState(int state) { + static int getIconForState(int state) { int iconRes; switch (state) { case STATE_LOCKED: @@ -343,11 +196,7 @@ public class LockIcon extends KeyguardAffordanceView implements return iconRes; } - private boolean doesAnimationLoop(@LockAnimIndex int lockAnimIndex) { - return lockAnimIndex == SCANNING; - } - - private static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, + static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { // Never animate when screen is off @@ -367,42 +216,10 @@ public class LockIcon extends KeyguardAffordanceView implements return -1; } - public void setBouncerShowingScrimmed(boolean bouncerShowing) { - mBouncerShowingScrimmed = bouncerShowing; - if (mBypassController.getBypassEnabled()) { - update(); - } - } - - /** - * Animate padlock opening when bouncer challenge is solved. - */ - public void onBouncerPreHideAnimation() { - mBouncerPreHideAnimation = true; - update(); - } - - void setIconColor(int iconColor) { - mIconColor = iconColor; - updateDarkTint(); - } - - void setSimLocked(boolean simLocked) { - mSimLocked = simLocked; - } - - /** Set if the device is docked. */ - public void setDocked(boolean docked) { - if (mDocked != docked) { - mDocked = docked; - update(); - } - } - @Retention(RetentionPolicy.SOURCE) @IntDef({ERROR, UNLOCK, LOCK, SCANNING}) @interface LockAnimIndex {} - private static final int ERROR = 0, UNLOCK = 1, LOCK = 2, SCANNING = 3; + static final int ERROR = 0, UNLOCK = 1, LOCK = 2, SCANNING = 3; private static final int[][] LOCK_ANIM_RES_IDS = new int[][] { { R.anim.lock_to_error, @@ -433,7 +250,7 @@ public class LockIcon extends KeyguardAffordanceView implements private int getThemedAnimationResId(@LockAnimIndex int lockAnimIndex) { final String setting = TextUtils.emptyIfNull( Settings.Secure.getString(getContext().getContentResolver(), - Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)); + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)); if (setting.contains("com.android.theme.icon_pack.circular.android")) { return LOCK_ANIM_RES_IDS[1][lockAnimIndex]; } else if (setting.contains("com.android.theme.icon_pack.filled.android")) { @@ -444,83 +261,8 @@ public class LockIcon extends KeyguardAffordanceView implements return LOCK_ANIM_RES_IDS[0][lockAnimIndex]; } - private int getState() { - KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class); - if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing - || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) { - return STATE_LOCK_OPEN; - } else if (mTransientBiometricsError) { - return STATE_BIOMETRICS_ERROR; - } else if (updateMonitor.isFaceDetectionRunning() && !mPulsing) { - return STATE_SCANNING_FACE; - } else { - return STATE_LOCKED; - } - } - - /** - * When keyguard is in pulsing (AOD2) state. - * @param pulsing {@code true} when pulsing. - */ - public void setPulsing(boolean pulsing) { - mPulsing = pulsing; - update(); + interface StateProvider { + int getState(); } - private void updateDarkTint() { - int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount); - setImageTintList(ColorStateList.valueOf(color)); - } - - /** - * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the - * icon on top of the black front scrim. - * @param wakeAndUnlock are we wake and unlocking - * @param isUnlock are we currently unlocking - */ - public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock) { - if (wakeAndUnlock) { - mWakeAndUnlockRunning = true; - } - if (isUnlock && mBypassController.getBypassEnabled() && canBlockUpdates()) { - // We don't want the icon to change while we are unlocking - mBlockUpdates = true; - } - update(); - } - - /** - * When we're launching an affordance, like double pressing power to open camera. - */ - public void onShowingLaunchAffordanceChanged(boolean showing) { - mShowingLaunchAffordance = showing; - update(); - } - - /** - * Called whenever the scrims become opaque, transparent or semi-transparent. - */ - public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) { - if (mWakeAndUnlockRunning - && scrimsVisible == ScrimController.TRANSPARENT) { - mWakeAndUnlockRunning = false; - update(); - } - } - - void setDozing(boolean dozing) { - mDozing = dozing; - update(); - } - - void setDozeAmount(float dozeAmount) { - mDozeAmount = dozeAmount; - updateDarkTint(); - } - - /** Set the StatusBarState. */ - public void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; - updateIconVisibility(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java index 2b1a8a472d9e..f7c861b84a68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java @@ -16,11 +16,19 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.LockIcon.STATE_BIOMETRICS_ERROR; +import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCKED; +import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCK_OPEN; +import static com.android.systemui.statusbar.phone.LockIcon.STATE_SCANNING_FACE; + +import android.content.res.Configuration; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import androidx.annotation.Nullable; @@ -29,15 +37,18 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.Optional; @@ -59,6 +70,21 @@ public class LockscreenLockIconController { private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; private final KeyguardBypassController mKeyguardBypassController; private final Optional<DockManager> mDockManager; + private final KeyguardStateController mKeyguardStateController; + private final Resources mResources; + private final HeadsUpManagerPhone mHeadsUpManagerPhone; + private boolean mKeyguardShowing; + private boolean mKeyguardJustShown; + private boolean mBlockUpdates; + private boolean mPulsing; + private boolean mDozing; + private boolean mSimLocked; + private boolean mTransientBiometricsError; + private boolean mDocked; + private boolean mWakeAndUnlockRunning; + private boolean mShowingLaunchAffordance; + private boolean mBouncerShowingScrimmed; + private int mStatusBarState = StatusBarState.SHADE; private LockIcon mLockIcon; private View.OnAttachStateChangeListener mOnAttachStateChangeListener = @@ -69,10 +95,13 @@ public class LockscreenLockIconController { mConfigurationController.addCallback(mConfigurationListener); mNotificationWakeUpCoordinator.addListener(mWakeUpListener); mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); + mKeyguardStateController.addCallback(mKeyguardMonitorCallback); mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener)); + mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure(); mConfigurationListener.onThemeChanged(); + update(); } @Override @@ -81,7 +110,7 @@ public class LockscreenLockIconController { mConfigurationController.removeCallback(mConfigurationListener); mNotificationWakeUpCoordinator.removeListener(mWakeUpListener); mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); - + mKeyguardStateController.removeCallback(mKeyguardMonitorCallback); mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener)); } @@ -91,32 +120,44 @@ public class LockscreenLockIconController { new StatusBarStateController.StateListener() { @Override public void onDozingChanged(boolean isDozing) { - mLockIcon.setDozing(isDozing); + setDozing(isDozing); } @Override public void onDozeAmountChanged(float linear, float eased) { - mLockIcon.setDozeAmount(eased); + if (mLockIcon != null) { + mLockIcon.setDozeAmount(eased); + } } @Override public void onStateChanged(int newState) { - mLockIcon.setStatusBarState(newState); + setStatusBarState(newState); } }; private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { + private int mDensity; + @Override public void onThemeChanged() { + if (mLockIcon == null) { + return; + } + TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes( null, new int[]{ R.attr.wallpaperTextColor }, 0, 0); int iconColor = typedArray.getColor(0, Color.WHITE); typedArray.recycle(); - mLockIcon.setIconColor(iconColor); + mLockIcon.onThemeChange(iconColor); } @Override public void onDensityOrFontScaleChanged() { + if (mLockIcon == null) { + return; + } + ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams(); if (lp == null) { return; @@ -125,24 +166,41 @@ public class LockscreenLockIconController { lp.height = mLockIcon.getResources().getDimensionPixelSize( R.dimen.keyguard_lock_height); mLockIcon.setLayoutParams(lp); - mLockIcon.update(true /* force */); + update(true /* force */); } @Override public void onLocaleListChanged() { + if (mLockIcon == null) { + return; + } + mLockIcon.setContentDescription( mLockIcon.getResources().getText(R.string.accessibility_unlock_button)); - mLockIcon.update(true /* force */); + update(true /* force */); + } + + @Override + public void onConfigChanged(Configuration newConfig) { + final int density = newConfig.densityDpi; + if (density != mDensity) { + mDensity = density; + update(); + } } }; private final WakeUpListener mWakeUpListener = new WakeUpListener() { @Override + public void onPulseExpansionChanged(boolean expandingChanged) { + } + + @Override public void onFullyHiddenChanged(boolean isFullyHidden) { if (mKeyguardBypassController.getBypassEnabled()) { - boolean changed = mLockIcon.updateIconVisibility(); + boolean changed = updateIconVisibility(); if (changed) { - mLockIcon.update(); + update(); } } } @@ -152,30 +210,103 @@ public class LockscreenLockIconController { new KeyguardUpdateMonitorCallback() { @Override public void onSimStateChanged(int subId, int slotId, int simState) { - mLockIcon.setSimLocked(mKeyguardUpdateMonitor.isSimPinSecure()); - mLockIcon.update(); + mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure(); + update(); } @Override public void onKeyguardVisibilityChanged(boolean showing) { - mLockIcon.update(); + update(); } @Override public void onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType) { - mLockIcon.update(); + update(); } @Override public void onStrongAuthStateChanged(int userId) { - mLockIcon.update(); + update(); } }; private final DockManager.DockEventListener mDockEventListener = - event -> mLockIcon.setDocked(event == DockManager.STATE_DOCKED - || event == DockManager.STATE_DOCKED_HIDE); + event -> { + boolean docked = + event == DockManager.STATE_DOCKED || event == DockManager.STATE_DOCKED_HIDE; + if (docked != mDocked) { + mDocked = docked; + update(); + } + }; + + private final KeyguardStateController.Callback mKeyguardMonitorCallback = + new KeyguardStateController.Callback() { + @Override + public void onKeyguardShowingChanged() { + boolean force = false; + boolean wasShowing = mKeyguardShowing; + mKeyguardShowing = mKeyguardStateController.isShowing(); + if (!wasShowing && mKeyguardShowing && mBlockUpdates) { + mBlockUpdates = false; + force = true; + } + if (!wasShowing && mKeyguardShowing) { + mKeyguardJustShown = true; + } + update(force); + } + + @Override + public void onKeyguardFadingAwayChanged() { + if (!mKeyguardStateController.isKeyguardFadingAway()) { + if (mBlockUpdates) { + mBlockUpdates = false; + update(true /* force */); + } + } + } + + @Override + public void onUnlockedChanged() { + update(); + } + }; + + private final View.AccessibilityDelegate mAccessibilityDelegate = + new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + boolean fingerprintRunning = + mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); + // Only checking if unlocking with Biometric is allowed (no matter strong or + // non-strong as long as primary auth, i.e. PIN/pattern/password, is not + // required), so it's ok to pass true for isStrongBiometric to + // isUnlockingWithBiometricAllowed() to bypass the check of whether non-strong + // biometric is allowed + boolean unlockingAllowed = mKeyguardUpdateMonitor + .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */); + if (fingerprintRunning && unlockingAllowed) { + AccessibilityNodeInfo.AccessibilityAction unlock = + new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, + mResources.getString( + R.string.accessibility_unlock_without_fingerprint)); + info.addAction(unlock); + info.setHintText(mResources.getString( + R.string.accessibility_waiting_for_fingerprint)); + } else if (getState() == STATE_SCANNING_FACE) { + //Avoid 'button' to be spoken for scanning face + info.setClassName(LockIcon.class.getName()); + info.setContentDescription(mResources.getString( + R.string.accessibility_scanning_face)); + } + } + }; + private int mLastState; @Inject public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger, @@ -188,7 +319,10 @@ public class LockscreenLockIconController { ConfigurationController configurationController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, KeyguardBypassController keyguardBypassController, - @Nullable DockManager dockManager) { + @Nullable DockManager dockManager, + KeyguardStateController keyguardStateController, + @Main Resources resources, + HeadsUpManagerPhone headsUpManagerPhone) { mLockscreenGestureLogger = lockscreenGestureLogger; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; @@ -200,24 +334,31 @@ public class LockscreenLockIconController { mNotificationWakeUpCoordinator = notificationWakeUpCoordinator; mKeyguardBypassController = keyguardBypassController; mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager); + mKeyguardStateController = keyguardStateController; + mResources = resources; + mHeadsUpManagerPhone = headsUpManagerPhone; mKeyguardIndicationController.setLockIconController(this); } /** * Associate the controller with a {@link LockIcon} + * + * TODO: change to an init method and inject the view. */ public void attach(LockIcon lockIcon) { mLockIcon = lockIcon; mLockIcon.setOnClickListener(this::handleClick); mLockIcon.setOnLongClickListener(this::handleLongClick); + mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); + mLockIcon.setStateProvider(this::getState); if (mLockIcon.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon); } mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener); - mLockIcon.setStatusBarState(mStatusBarStateController.getState()); + setStatusBarState(mStatusBarStateController.getState()); } public LockIcon getView() { @@ -228,8 +369,10 @@ public class LockscreenLockIconController { * Called whenever the scrims become opaque, transparent or semi-transparent. */ public void onScrimVisibilityChanged(Integer scrimsVisible) { - if (mLockIcon != null) { - mLockIcon.onScrimVisibilityChanged(scrimsVisible); + if (mWakeAndUnlockRunning + && scrimsVisible == ScrimController.TRANSPARENT) { + mWakeAndUnlockRunning = false; + update(); } } @@ -237,55 +380,56 @@ public class LockscreenLockIconController { * Propagate {@link StatusBar} pulsing state. */ public void setPulsing(boolean pulsing) { - if (mLockIcon != null) { - mLockIcon.setPulsing(pulsing); - } + mPulsing = pulsing; + update(); } /** - * Called when the biometric authentication mode changes. - * - * @param wakeAndUnlock If the type is {@link BiometricUnlockController#isWakeAndUnlock()} - * @param isUnlock If the type is {@link BiometricUnlockController#isBiometricUnlock()} () + * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the + * icon on top of the black front scrim. + * @param wakeAndUnlock are we wake and unlocking + * @param isUnlock are we currently unlocking */ public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock) { - if (mLockIcon != null) { - mLockIcon.onBiometricAuthModeChanged(wakeAndUnlock, isUnlock); + if (wakeAndUnlock) { + mWakeAndUnlockRunning = true; + } + if (isUnlock && mKeyguardBypassController.getBypassEnabled() && canBlockUpdates()) { + // We don't want the icon to change while we are unlocking + mBlockUpdates = true; } + update(); } /** * When we're launching an affordance, like double pressing power to open camera. */ public void onShowingLaunchAffordanceChanged(Boolean showing) { - if (mLockIcon != null) { - mLockIcon.onShowingLaunchAffordanceChanged(showing); - } + mShowingLaunchAffordance = showing; + update(); } /** Sets whether the bouncer is showing. */ public void setBouncerShowingScrimmed(boolean bouncerShowing) { - if (mLockIcon != null) { - mLockIcon.setBouncerShowingScrimmed(bouncerShowing); + mBouncerShowingScrimmed = bouncerShowing; + if (mKeyguardBypassController.getBypassEnabled()) { + update(); } } /** - * When {@link KeyguardBouncer} starts to be dismissed and starts to play its animation. + * Animate padlock opening when bouncer challenge is solved. */ public void onBouncerPreHideAnimation() { - if (mLockIcon != null) { - mLockIcon.onBouncerPreHideAnimation(); - } + update(); } /** * If we're currently presenting an authentication error message. */ public void setTransientBiometricsError(boolean transientBiometricsError) { - if (mLockIcon != null) { - mLockIcon.setTransientBiometricsError(transientBiometricsError); - } + mTransientBiometricsError = transientBiometricsError; + update(); } private boolean handleLongClick(View view) { @@ -306,4 +450,90 @@ public class LockscreenLockIconController { } mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); } + + private void update() { + update(false /* force */); + } + + private void update(boolean force) { + int state = getState(); + boolean shouldUpdate = mLastState != state || force; + if (mBlockUpdates && canBlockUpdates()) { + shouldUpdate = false; + } + if (shouldUpdate && mLockIcon != null) { + mLockIcon.update(mLastState, mPulsing, mDozing, mKeyguardJustShown); + } + mLastState = state; + mKeyguardJustShown = false; + updateIconVisibility(); + updateClickability(); + } + + private int getState() { + if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing + || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) { + return STATE_LOCK_OPEN; + } else if (mTransientBiometricsError) { + return STATE_BIOMETRICS_ERROR; + } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning() && !mPulsing) { + return STATE_SCANNING_FACE; + } else { + return STATE_LOCKED; + } + } + + private boolean canBlockUpdates() { + return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway(); + } + + private void setDozing(boolean isDozing) { + mDozing = isDozing; + update(); + } + + /** Set the StatusBarState. */ + private void setStatusBarState(int statusBarState) { + mStatusBarState = statusBarState; + updateIconVisibility(); + } + + /** + * Update the icon visibility + * @return true if the visibility changed + */ + private boolean updateIconVisibility() { + boolean onAodNotPulsingOrDocked = mDozing && (!mPulsing || mDocked); + boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning + || mShowingLaunchAffordance; + if (mKeyguardBypassController.getBypassEnabled() && !mBouncerShowingScrimmed) { + if ((mHeadsUpManagerPhone.isHeadsUpGoingAway() + || mHeadsUpManagerPhone.hasPinnedHeadsUp() + || mStatusBarState == StatusBarState.KEYGUARD) + && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) { + invisible = true; + } + } + + if (mLockIcon == null) { + return false; + } + + return mLockIcon.updateIconVisibility(!invisible); + } + + private void updateClickability() { + if (mAccessibilityController == null) { + return; + } + boolean canLock = mKeyguardStateController.isMethodSecure() + && mKeyguardStateController.canDismissLockScreen(); + boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled(); + if (mLockIcon != null) { + mLockIcon.setClickable(clickToUnlock); + mLockIcon.setLongClickable(canLock && !clickToUnlock); + mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled()); + } + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 1c1e7c4eaa4a..977a307a3d95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -66,7 +66,7 @@ public class PhoneStatusBarView extends PanelBar { } }; private DarkReceiver mBattery; - private int mRotationOrientation; + private int mRotationOrientation = -1; @Nullable private View mCenterIconSpace; @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 760a6d6a71c3..c6f79831e022 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -99,11 +99,12 @@ public class StatusBarWindowView extends FrameLayout { } // padding needed for corner cutout. - int leftCornerCutoutPadding = 0; - int rightCornerCutoutPadding = 0; + int leftCornerCutoutPadding = cutout.getSafeInsetLeft(); + int rightCornerCutoutPadding = cutout.getSafeInsetRight(); if (cornerCutoutPadding != null) { - leftCornerCutoutPadding = cornerCutoutPadding.first; - rightCornerCutoutPadding = cornerCutoutPadding.second; + leftCornerCutoutPadding = Math.max(leftCornerCutoutPadding, cornerCutoutPadding.first); + rightCornerCutoutPadding = Math.max(rightCornerCutoutPadding, + cornerCutoutPadding.second); } return new Pair<>( diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java index 56aae17f451e..c63712389a80 100644 --- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java +++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java @@ -34,7 +34,6 @@ import com.android.systemui.qs.QuickStatusBarHeader; import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.phone.LockIcon; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -148,11 +147,6 @@ public class InjectionInflationController { KeyguardMessageArea createKeyguardMessageArea(); /** - * Creates the keyguard LockIcon. - */ - LockIcon createLockIcon(); - - /** * Creates the QSPanel. */ QSPanel createQSPanel(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 6e612d7124ed..4f16031741bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -62,7 +62,9 @@ import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; @@ -136,6 +138,9 @@ public class BubbleControllerTest extends SysuiTestCase { @Mock private FloatingContentCoordinator mFloatingContentCoordinator; + private SysUiState mSysUiState; + private boolean mSysUiStateBubblesExpanded; + @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; @Captor @@ -229,6 +234,11 @@ public class BubbleControllerTest extends SysuiTestCase { mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); + mSysUiState = new SysUiState(); + mSysUiState.addCallback(sysUiFlags -> + mSysUiStateBubblesExpanded = + (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0); + TestableNotificationInterruptStateProviderImpl interruptionStateProvider = new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), mock(PowerManager.class), @@ -257,7 +267,8 @@ public class BubbleControllerTest extends SysuiTestCase { mNotifPipeline, mFeatureFlagsOldPipeline, mDumpManager, - mFloatingContentCoordinator); + mFloatingContentCoordinator, + mSysUiState); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); @@ -277,6 +288,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); verify(mBubbleStateChangeListener).onHasBubblesChanged(true); + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -284,6 +296,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertFalse(mBubbleController.hasBubbles()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -300,6 +313,8 @@ public class BubbleControllerTest extends SysuiTestCase { assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); verify(mNotificationEntryManager, times(2)).updateNotifications(anyString()); verify(mBubbleStateChangeListener).onHasBubblesChanged(false); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -323,6 +338,8 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mNotificationEntryManager, times(1)).performRemoveNotification( eq(mRow.getEntry().getSbn()), anyInt()); assertFalse(mBubbleController.hasBubbles()); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -340,6 +357,8 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mNotificationEntryManager, times(3)).updateNotifications(any()); assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey())); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -363,6 +382,8 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); assertTrue(mNotificationShadeWindowController.getBubbleExpanded()); + assertTrue(mSysUiStateBubblesExpanded); + // Make sure the notif is suppressed assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -372,6 +393,8 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); assertFalse(mBubbleController.isStackExpanded()); assertFalse(mNotificationShadeWindowController.getBubbleExpanded()); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -395,6 +418,8 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + assertTrue(mSysUiStateBubblesExpanded); + // Last added is the one that is expanded assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -416,6 +441,8 @@ public class BubbleControllerTest extends SysuiTestCase { // Collapse mBubbleController.collapseStack(); assertFalse(mBubbleController.isStackExpanded()); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -437,6 +464,8 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + assertTrue(mSysUiStateBubblesExpanded); + // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -463,6 +492,8 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + assertTrue(mSysUiStateBubblesExpanded); + // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -493,6 +524,8 @@ public class BubbleControllerTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleController.expandStack(); + assertTrue(mSysUiStateBubblesExpanded); + assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); @@ -522,6 +555,8 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); verify(mBubbleStateChangeListener).onHasBubblesChanged(false); assertFalse(mBubbleController.hasBubbles()); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -541,6 +576,8 @@ public class BubbleControllerTest extends SysuiTestCase { // # of bubbles should change verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -559,6 +596,8 @@ public class BubbleControllerTest extends SysuiTestCase { // # of bubbles should change verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + + assertTrue(mSysUiStateBubblesExpanded); } @Test @@ -579,6 +618,8 @@ public class BubbleControllerTest extends SysuiTestCase { // # of bubbles should change verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -605,6 +646,8 @@ public class BubbleControllerTest extends SysuiTestCase { // # of bubbles should change verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + + assertFalse(mSysUiStateBubblesExpanded); } @Test @@ -619,6 +662,8 @@ public class BubbleControllerTest extends SysuiTestCase { mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL); mBubbleController.expandStackAndSelectBubble(key); + + assertTrue(mSysUiStateBubblesExpanded); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 624464401a4a..a31e3f8d7cc9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -58,6 +58,7 @@ import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -132,6 +133,9 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { private KeyguardBypassController mKeyguardBypassController; @Mock private FloatingContentCoordinator mFloatingContentCoordinator; + + private SysUiState mSysUiState = new SysUiState(); + @Captor private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor; private TestableBubbleController mBubbleController; @@ -242,7 +246,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mNotifPipeline, mFeatureFlagsNewPipeline, mDumpManager, - mFloatingContentCoordinator); + mFloatingContentCoordinator, + mSysUiState); mBubbleController.addNotifCallback(mNotifCallback); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index d3d90c408468..f4861028e81a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -19,6 +19,7 @@ package com.android.systemui.bubbles; import android.content.Context; import com.android.systemui.dump.DumpManager; +import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -52,12 +53,13 @@ public class TestableBubbleController extends BubbleController { NotifPipeline notifPipeline, FeatureFlags featureFlags, DumpManager dumpManager, - FloatingContentCoordinator floatingContentCoordinator) { + FloatingContentCoordinator floatingContentCoordinator, + SysUiState sysUiState) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, - notifPipeline, featureFlags, dumpManager, floatingContentCoordinator); + notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState); setInflateSynchronously(true); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index f061f34072d0..f4583f99f2d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -22,7 +22,6 @@ import android.testing.TestableLooper.RunWithLooper import android.view.Choreographer import android.view.View import android.view.ViewRootImpl -import androidx.dynamicanimation.animation.SpringAnimation import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -35,10 +34,14 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Mock -import org.mockito.Mockito.* +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.anyString +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @@ -56,7 +59,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var root: View @Mock private lateinit var viewRootImpl: ViewRootImpl - @Mock private lateinit var shadeSpring: SpringAnimation + @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation + @Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation @JvmField @Rule val mockitoRule = MockitoJUnit.rule() private lateinit var statusBarStateListener: StatusBarStateController.StateListener @@ -76,6 +80,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { keyguardStateController, choreographer, wallpaperManager, notificationShadeWindowController, dumpManager) notificationShadeDepthController.shadeSpring = shadeSpring + notificationShadeDepthController.globalActionsSpring = globalActionsSpring notificationShadeDepthController.root = root val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java) @@ -92,7 +97,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun onPanelExpansionChanged_apliesBlur_ifShade() { notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */, false /* tracking */) - verify(shadeSpring).animateToFinalPosition(eq(maxBlur.toFloat())) + verify(shadeSpring).animateTo(eq(maxBlur), any()) } @Test @@ -102,13 +107,13 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { statusBarState = StatusBarState.KEYGUARD statusBarStateListener.onStateChanged(statusBarState) - verify(shadeSpring).animateToFinalPosition(eq(0f)) + verify(shadeSpring).animateTo(eq(0), any()) } @Test - fun updateGlobalDialogVisibility_schedulesUpdate() { + fun updateGlobalDialogVisibility_appliesBlur() { notificationShadeDepthController.updateGlobalDialogVisibility(0.5f, root) - verify(choreographer).postFrameCallback(any()) + verify(globalActionsSpring).animateTo(eq(maxBlur / 2), safeEq(root)) } private fun <T : Any> safeEq(value: T): T { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java index 261dc829c7e2..f4fbd7b7d8a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java @@ -48,7 +48,7 @@ public class NotificationEntryBuilder { /* ListEntry properties */ private GroupEntry mParent; - private int mSection; + private int mSection = -1; public NotificationEntry build() { StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java new file mode 100644 index 000000000000..87fc02062ad4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.util.SparseArray; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable.PluggableListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class HideNotifsForOtherUsersCoordinatorTest extends SysuiTestCase { + + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock private NotifPipeline mNotifPipeline; + @Mock private PluggableListener<NotifFilter> mInvalidationListener; + + @Captor private ArgumentCaptor<UserChangedListener> mUserChangedListenerCaptor; + @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor; + + private UserChangedListener mCapturedUserChangeListener; + private NotifFilter mCapturedNotifFilter; + + private NotificationEntry mEntry = new NotificationEntryBuilder().build(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + HideNotifsForOtherUsersCoordinator coordinator = + new HideNotifsForOtherUsersCoordinator(mLockscreenUserManager); + coordinator.attach(mNotifPipeline); + + verify(mLockscreenUserManager).addUserChangedListener(mUserChangedListenerCaptor.capture()); + verify(mNotifPipeline).addPreGroupFilter(mNotifFilterCaptor.capture()); + + mCapturedUserChangeListener = mUserChangedListenerCaptor.getValue(); + mCapturedNotifFilter = mNotifFilterCaptor.getValue(); + + mCapturedNotifFilter.setInvalidationListener(mInvalidationListener); + } + + @Test + public void testFilterOutNotifsFromOtherProfiles() { + // GIVEN that all notifs are NOT for the current user + when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false); + + // THEN they should all be filtered out + assertTrue(mCapturedNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void testPreserveNotifsFromThisProfile() { + // GIVEN that all notifs ARE for the current user + when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(true); + + // THEN none should be filtered out + assertFalse(mCapturedNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void testFilterIsInvalidatedWhenProfilesChange() { + // WHEN the current user profiles change + mCapturedUserChangeListener.onCurrentProfilesChanged(new SparseArray<>()); + + // THEN the filter is invalidated + verify(mInvalidationListener).onPluggableInvalidated(mCapturedNotifFilter); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java index c4f3a1611afc..4f481081855f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -102,16 +102,6 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { } @Test - public void notificationNotForCurrentProfile() { - // GIVEN the notification isn't for the given user - setupUnfilteredState(mEntry); - when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(false); - - // THEN filter out the entry - assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0)); - } - - @Test public void keyguardNotShowing() { // GIVEN the lockscreen isn't showing setupUnfilteredState(mEntry); @@ -229,9 +219,6 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { * KeyguardNotificationCoordinator when the keyguard is showing. */ private void setupUnfilteredState(NotificationEntry entry) { - // notification is for current profile - when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(true); - // keyguard is showing when(mKeyguardStateController.isShowing()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index e84f9cf352ed..85acbe6d440b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -19,9 +19,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,6 +41,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -51,20 +51,23 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private NotifPipeline mNotifPipeline; + + @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor; + private NotificationEntry mEntry; - private RankingCoordinator mRankingCoordinator; - private NotifFilter mRankingFilter; + private NotifFilter mCapturedSuspendedFilter; + private NotifFilter mCapturedDozingFilter; @Before public void setup() { MockitoAnnotations.initMocks(this); - mRankingCoordinator = new RankingCoordinator(mStatusBarStateController); + RankingCoordinator rankingCoordinator = new RankingCoordinator(mStatusBarStateController); mEntry = new NotificationEntryBuilder().build(); - ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mRankingCoordinator.attach(mNotifPipeline); - verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); - mRankingFilter = filterCaptor.getValue(); + rankingCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture()); + mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0); + mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1); } @Test @@ -73,7 +76,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { mEntry.setRanking(getRankingForUnfilteredNotif().build()); // THEN don't filter out the notification - assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertFalse(mCapturedSuspendedFilter.shouldFilterOut(mEntry, 0)); } @Test @@ -84,7 +87,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { .build()); // THEN filter out the notification - assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertTrue(mCapturedSuspendedFilter.shouldFilterOut(mEntry, 0)); } @Test @@ -98,13 +101,13 @@ public class RankingCoordinatorTest extends SysuiTestCase { when(mStatusBarStateController.isDozing()).thenReturn(true); // THEN filter out the notification - assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); // WHEN it's not dozing (showing the notification list) when(mStatusBarStateController.isDozing()).thenReturn(false); // THEN don't filter out the notification - assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertFalse(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); } @Test @@ -118,13 +121,13 @@ public class RankingCoordinatorTest extends SysuiTestCase { when(mStatusBarStateController.isDozing()).thenReturn(true); // THEN don't filter out the notification - assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertFalse(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); // WHEN it's not dozing (showing the notification list) when(mStatusBarStateController.isDozing()).thenReturn(false); // THEN filter out the notification - assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); } private RankingBuilder getRankingForUnfilteredNotif() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java index 487885ac244e..85b5d70c883c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.res.Resources; import android.view.View; import androidx.test.filters.SmallTest; @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; @@ -71,6 +73,12 @@ public class LockscreenIconControllerTest extends SysuiTestCase { private KeyguardBypassController mKeyguardBypassController; @Mock private DockManager mDockManager; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private Resources mResources; + @Mock + private HeadsUpManagerPhone mHeadsUpManagerPhone; @Before @@ -81,7 +89,8 @@ public class LockscreenIconControllerTest extends SysuiTestCase { mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils, mShadeController, mAccessibilityController, mKeyguardIndicationController, mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator, - mKeyguardBypassController, mDockManager); + mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources, + mHeadsUpManagerPhone); mLockIconController.attach(mLockIcon); } diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp index 2fbba68f1e03..00d0d9c428ff 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -60,6 +60,7 @@ java_library { hostdex: true, // for hiddenapi check visibility: ["//frameworks/base/packages/Tethering:__subpackages__"], apex_available: ["com.android.tethering"], + permitted_packages: ["android.net"], } stubs_defaults { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index e49c1ed47c93..c6a54fc3d206 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -50,6 +50,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -63,6 +64,7 @@ import android.util.LocalLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.SmartSuggestionMode; @@ -151,6 +153,7 @@ public final class AutofillManagerService private final LocalLog mWtfHistory = new LocalLog(50); private final AutofillCompatState mAutofillCompatState = new AutofillCompatState(); + private final DisabledInfoCache mDisabledInfoCache = new DisabledInfoCache(); private final LocalService mLocalService = new LocalService(); private final ActivityManagerInternal mAm; @@ -302,14 +305,15 @@ public final class AutofillManagerService @Override // from AbstractMasterSystemService protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled) { - return new AutofillManagerServiceImpl(this, mLock, mUiLatencyHistory, - mWtfHistory, resolvedUserId, mUi, mAutofillCompatState, disabled); + return new AutofillManagerServiceImpl(this, mLock, mUiLatencyHistory, mWtfHistory, + resolvedUserId, mUi, mAutofillCompatState, disabled, mDisabledInfoCache); } @Override // AbstractMasterSystemService protected void onServiceRemoved(@NonNull AutofillManagerServiceImpl service, @UserIdInt int userId) { service.destroyLocked(); + mDisabledInfoCache.remove(userId); mAutofillCompatState.removeCompatibilityModeRequests(userId); } @@ -835,15 +839,10 @@ public final class AutofillManagerService private void injectDisableAppInfo(@NonNull AutofillOptions options, int userId, String packageName) { - synchronized (mLock) { - final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); - if (service != null) { - options.appDisabledExpiration = service.getAppDisabledExpirationLocked( - packageName); - options.disabledActivities = service.getAppDisabledActivitiesLocked( - packageName); - } - } + options.appDisabledExpiration = + mDisabledInfoCache.getAppDisabledExpiration(userId, packageName); + options.disabledActivities = + mDisabledInfoCache.getAppDisabledActivities(userId, packageName); } } @@ -867,6 +866,234 @@ public final class AutofillManagerService } /** + * Stores autofill disable information, i.e. {@link AutofillDisabledInfo}, keyed by user id. + * The information is cleaned up when the service is removed. + */ + static final class DisabledInfoCache { + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final SparseArray<AutofillDisabledInfo> mCache = new SparseArray<>(); + + void remove(@UserIdInt int userId) { + synchronized (mLock) { + mCache.remove(userId); + } + } + + void addDisabledAppLocked(@UserIdInt int userId, @NonNull String packageName, + long expiration) { + Preconditions.checkNotNull(packageName); + synchronized (mLock) { + AutofillDisabledInfo info = + getOrCreateAutofillDisabledInfoByUserIdLocked(userId); + info.putDisableAppsLocked(packageName, expiration); + } + } + + void addDisabledActivityLocked(@UserIdInt int userId, @NonNull ComponentName componentName, + long expiration) { + Preconditions.checkNotNull(componentName); + synchronized (mLock) { + AutofillDisabledInfo info = + getOrCreateAutofillDisabledInfoByUserIdLocked(userId); + info.putDisableActivityLocked(componentName, expiration); + } + } + + boolean isAutofillDisabledLocked(@UserIdInt int userId, + @NonNull ComponentName componentName) { + Preconditions.checkNotNull(componentName); + final boolean disabled; + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + disabled = info != null ? info.isAutofillDisabledLocked(componentName) : false; + } + return disabled; + } + + long getAppDisabledExpiration(@UserIdInt int userId, @NonNull String packageName) { + Preconditions.checkNotNull(packageName); + final Long expiration; + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + expiration = info != null ? info.getAppDisabledExpirationLocked(packageName) : 0; + } + return expiration; + } + + @Nullable + ArrayMap<String, Long> getAppDisabledActivities(@UserIdInt int userId, + @NonNull String packageName) { + Preconditions.checkNotNull(packageName); + final ArrayMap<String, Long> disabledList; + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + disabledList = + info != null ? info.getAppDisabledActivitiesLocked(packageName) : null; + } + return disabledList; + } + + void dump(@UserIdInt int userId, String prefix, PrintWriter pw) { + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + if (info != null) { + info.dumpLocked(prefix, pw); + } + } + } + + @NonNull + private AutofillDisabledInfo getOrCreateAutofillDisabledInfoByUserIdLocked( + @UserIdInt int userId) { + AutofillDisabledInfo info = mCache.get(userId); + if (info == null) { + info = new AutofillDisabledInfo(); + mCache.put(userId, info); + } + return info; + } + } + + /** + * The autofill disable information. + * <p> + * This contains disable information set by the AutofillService, e.g. disabled application + * expiration, disable activity expiration. + */ + private static final class AutofillDisabledInfo { + /** + * Apps disabled by the service; key is package name, value is when they will be enabled + * again. + */ + private ArrayMap<String, Long> mDisabledApps; + /** + * Activities disabled by the service; key is component name, value is when they will be + * enabled again. + */ + private ArrayMap<ComponentName, Long> mDisabledActivities; + + void putDisableAppsLocked(@NonNull String packageName, long expiration) { + if (mDisabledApps == null) { + mDisabledApps = new ArrayMap<>(1); + } + mDisabledApps.put(packageName, expiration); + } + + void putDisableActivityLocked(@NonNull ComponentName componentName, long expiration) { + if (mDisabledActivities == null) { + mDisabledActivities = new ArrayMap<>(1); + } + mDisabledActivities.put(componentName, expiration); + } + + long getAppDisabledExpirationLocked(@NonNull String packageName) { + if (mDisabledApps == null) { + return 0; + } + final Long expiration = mDisabledApps.get(packageName); + return expiration != null ? expiration : 0; + } + + ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) { + if (mDisabledActivities != null) { + final int size = mDisabledActivities.size(); + ArrayMap<String, Long> disabledList = null; + for (int i = 0; i < size; i++) { + final ComponentName component = mDisabledActivities.keyAt(i); + if (packageName.equals(component.getPackageName())) { + if (disabledList == null) { + disabledList = new ArrayMap<>(); + } + final long expiration = mDisabledActivities.valueAt(i); + disabledList.put(component.flattenToShortString(), expiration); + } + } + return disabledList; + } + return null; + } + + boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { + // Check activities first. + long elapsedTime = 0; + if (mDisabledActivities != null) { + elapsedTime = SystemClock.elapsedRealtime(); + final Long expiration = mDisabledActivities.get(componentName); + if (expiration != null) { + if (expiration >= elapsedTime) return true; + // Restriction expired - clean it up. + if (sVerbose) { + Slog.v(TAG, "Removing " + componentName.toShortString() + + " from disabled list"); + } + mDisabledActivities.remove(componentName); + } + } + + // Then check apps. + final String packageName = componentName.getPackageName(); + if (mDisabledApps == null) return false; + + final Long expiration = mDisabledApps.get(packageName); + if (expiration == null) return false; + + if (elapsedTime == 0) { + elapsedTime = SystemClock.elapsedRealtime(); + } + + if (expiration >= elapsedTime) return true; + + // Restriction expired - clean it up. + if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list"); + mDisabledApps.remove(packageName); + return false; + } + + void dumpLocked(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("Disabled apps: "); + if (mDisabledApps == null) { + pw.println("N/A"); + } else { + final int size = mDisabledApps.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final String packageName = mDisabledApps.keyAt(i); + final long expiration = mDisabledApps.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(packageName).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + + pw.print(prefix); pw.print("Disabled activities: "); + if (mDisabledActivities == null) { + pw.println("N/A"); + } else { + final int size = mDisabledActivities.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final ComponentName component = mDisabledActivities.keyAt(i); + final long expiration = mDisabledActivities.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(component).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + } + } + + /** * Compatibility mode metadata associated with all services. * * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 6fbe1410bbad..d1805d96cad8 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -67,7 +67,6 @@ import android.util.LocalLog; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; -import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.SmartSuggestionMode; @@ -80,6 +79,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; import com.android.server.autofill.AutofillManagerService.AutofillCompatState; +import com.android.server.autofill.AutofillManagerService.DisabledInfoCache; import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.contentcapture.ContentCaptureManagerInternal; @@ -90,7 +90,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Random; - /** * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the * app's {@link IAutoFillService} implementation. @@ -125,19 +124,6 @@ final class AutofillManagerServiceImpl private RemoteInlineSuggestionRenderService mRemoteInlineSuggestionRenderService; /** - * Apps disabled by the service; key is package name, value is when they will be enabled again. - */ - @GuardedBy("mLock") - private ArrayMap<String, Long> mDisabledApps; - - /** - * Activities disabled by the service; key is component name, value is when they will be enabled - * again. - */ - @GuardedBy("mLock") - private ArrayMap<ComponentName, Long> mDisabledActivities; - - /** * Data used for field classification. */ @GuardedBy("mLock") @@ -186,10 +172,12 @@ final class AutofillManagerServiceImpl private final ContentCaptureManagerInternal mContentCaptureManagerInternal; + private final DisabledInfoCache mDisabledInfoCache; + AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, - boolean disabled) { + boolean disabled, DisabledInfoCache disableCache) { super(master, lock, userId); mUiLatencyHistory = uiLatencyHistory; @@ -200,7 +188,7 @@ final class AutofillManagerServiceImpl mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); mContentCaptureManagerInternal = LocalServices.getService( ContentCaptureManagerInternal.class); - + mDisabledInfoCache = disableCache; updateLocked(disabled); } @@ -1045,45 +1033,7 @@ final class AutofillManagerServiceImpl pw.println(isInlineSuggestionsEnabled()); pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); - pw.print(prefix); pw.print("Disabled apps: "); - - if (mDisabledApps == null) { - pw.println("N/A"); - } else { - final int size = mDisabledApps.size(); - pw.println(size); - final StringBuilder builder = new StringBuilder(); - final long now = SystemClock.elapsedRealtime(); - for (int i = 0; i < size; i++) { - final String packageName = mDisabledApps.keyAt(i); - final long expiration = mDisabledApps.valueAt(i); - builder.append(prefix).append(prefix) - .append(i).append(". ").append(packageName).append(": "); - TimeUtils.formatDuration((expiration - now), builder); - builder.append('\n'); - } - pw.println(builder); - } - - pw.print(prefix); pw.print("Disabled activities: "); - - if (mDisabledActivities == null) { - pw.println("N/A"); - } else { - final int size = mDisabledActivities.size(); - pw.println(size); - final StringBuilder builder = new StringBuilder(); - final long now = SystemClock.elapsedRealtime(); - for (int i = 0; i < size; i++) { - final ComponentName component = mDisabledActivities.keyAt(i); - final long expiration = mDisabledActivities.valueAt(i); - builder.append(prefix).append(prefix) - .append(i).append(". ").append(component).append(": "); - TimeUtils.formatDuration((expiration - now), builder); - builder.append('\n'); - } - pw.println(builder); - } + mDisabledInfoCache.dump(mUserId, prefix, pw); final int size = mSessions.size(); if (size == 0) { @@ -1480,15 +1430,13 @@ final class AutofillManagerServiceImpl void disableAutofillForApp(@NonNull String packageName, long duration, int sessionId, boolean compatMode) { synchronized (mLock) { - if (mDisabledApps == null) { - mDisabledApps = new ArrayMap<>(1); - } long expiration = SystemClock.elapsedRealtime() + duration; // Protect it against overflow if (expiration < 0) { expiration = Long.MAX_VALUE; } - mDisabledApps.put(packageName, expiration); + mDisabledInfoCache.addDisabledAppLocked(mUserId, packageName, expiration); + int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP, packageName, getServicePackageName(), sessionId, compatMode) @@ -1502,15 +1450,12 @@ final class AutofillManagerServiceImpl void disableAutofillForActivity(@NonNull ComponentName componentName, long duration, int sessionId, boolean compatMode) { synchronized (mLock) { - if (mDisabledActivities == null) { - mDisabledActivities = new ArrayMap<>(1); - } long expiration = SystemClock.elapsedRealtime() + duration; // Protect it against overflow if (expiration < 0) { expiration = Long.MAX_VALUE; } - mDisabledActivities.put(componentName, expiration); + mDisabledInfoCache.addDisabledActivityLocked(mUserId, componentName, expiration); final int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; @@ -1528,74 +1473,12 @@ final class AutofillManagerServiceImpl } } - // Called by AutofillManagerService - long getAppDisabledExpirationLocked(@NonNull String packageName) { - if (mDisabledApps == null) { - return 0; - } - final Long expiration = mDisabledApps.get(packageName); - return expiration != null ? expiration : 0; - } - - // Called by AutofillManagerService - @Nullable - ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) { - if (mDisabledActivities != null) { - final int size = mDisabledActivities.size(); - ArrayMap<String, Long> disabledList = null; - for (int i = 0; i < size; i++) { - final ComponentName component = mDisabledActivities.keyAt(i); - if (packageName.equals(component.getPackageName())) { - if (disabledList == null) { - disabledList = new ArrayMap<>(); - } - final long expiration = mDisabledActivities.valueAt(i); - disabledList.put(component.flattenToShortString(), expiration); - } - } - return disabledList; - } - return null; - } - /** * Checks if autofill is disabled by service to the given activity. */ @GuardedBy("mLock") private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { - // Check activities first. - long elapsedTime = 0; - if (mDisabledActivities != null) { - elapsedTime = SystemClock.elapsedRealtime(); - final Long expiration = mDisabledActivities.get(componentName); - if (expiration != null) { - if (expiration >= elapsedTime) return true; - // Restriction expired - clean it up. - if (sVerbose) { - Slog.v(TAG, "Removing " + componentName.toShortString() - + " from disabled list"); - } - mDisabledActivities.remove(componentName); - } - } - - // Then check apps. - final String packageName = componentName.getPackageName(); - if (mDisabledApps == null) return false; - - final Long expiration = mDisabledApps.get(packageName); - if (expiration == null) return false; - - if (elapsedTime == 0) { - elapsedTime = SystemClock.elapsedRealtime(); - } - - if (expiration >= elapsedTime) return true; - - // Restriction expired - clean it up. - if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list"); - mDisabledApps.remove(packageName); - return false; + return mDisabledInfoCache.isAutofillDisabledLocked(mUserId, componentName); } // Called by AutofillManager, checks UID. diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java index 5de817101177..4ba2c3d0a0d3 100644 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java @@ -199,7 +199,10 @@ final class InlineSuggestionSession { return false; } - if (!mImeInputViewStarted || !autofillId.equalsIgnoreSession(mImeFieldId)) { + // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we + // only compare the view id for now. + if (!mImeInputViewStarted || mImeFieldId == null + || autofillId.getViewId() != mImeFieldId.getViewId()) { if (sDebug) { Log.d(TAG, "onInlineSuggestionsResponseLocked not sent because input view is not " diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java index b54ec4ea9441..0fdabd09f9d4 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java @@ -21,13 +21,17 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.contentsuggestions.ClassificationsRequest; +import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IClassificationsCallback; import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.contentsuggestions.ISelectionsCallback; import android.app.contentsuggestions.SelectionsRequest; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; @@ -133,7 +137,7 @@ public class ContentSuggestionsManagerService extends if (service != null) { // TODO(b/147324195): Temporarily pass bitmap until we change the service API. imageContextRequestExtras.putParcelable(EXTRA_BITMAP, bitmap); - service.provideContextImageLocked(/* taskId = */ -1, imageContextRequestExtras); + service.provideContextImageFromBitmapLocked(imageContextRequestExtras); } else { if (VERBOSE) { Slog.v(TAG, "provideContextImageLocked: no service for " + userId); @@ -152,10 +156,28 @@ public class ContentSuggestionsManagerService extends } enforceCaller(UserHandle.getCallingUserId(), "provideContextImage"); + GraphicBuffer snapshotBuffer = null; + int colorSpaceId = 0; + + // Skip taking TaskSnapshot when bitmap is provided. + if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) { + // Can block, so call before acquiring the lock. + ActivityManager.TaskSnapshot snapshot = + mActivityTaskManagerInternal.getTaskSnapshotBlocking(taskId, false); + if (snapshot != null) { + snapshotBuffer = snapshot.getSnapshot(); + ColorSpace colorSpace = snapshot.getColorSpace(); + if (colorSpace != null) { + colorSpaceId = colorSpace.getId(); + } + } + } + synchronized (mLock) { final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId); if (service != null) { - service.provideContextImageLocked(taskId, imageContextRequestExtras); + service.provideContextImageLocked(taskId, snapshotBuffer, colorSpaceId, + imageContextRequestExtras); } else { if (VERBOSE) { Slog.v(TAG, "provideContextImageLocked: no service for " + userId); diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java index 7828050223f4..cf53b169515e 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java @@ -19,17 +19,14 @@ package com.android.server.contentsuggestions; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityManager; import android.app.AppGlobals; import android.app.contentsuggestions.ClassificationsRequest; -import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IClassificationsCallback; import android.app.contentsuggestions.ISelectionsCallback; import android.app.contentsuggestions.SelectionsRequest; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; -import android.graphics.ColorSpace; import android.graphics.GraphicBuffer; import android.os.Bundle; import android.os.RemoteException; @@ -95,26 +92,17 @@ public final class ContentSuggestionsPerUserService extends } @GuardedBy("mLock") - void provideContextImageLocked(int taskId, @NonNull Bundle imageContextRequestExtras) { + void provideContextImageFromBitmapLocked(@NonNull Bundle bitmapContainingExtras) { + // No task or snapshot provided, the bitmap is contained in the extras + provideContextImageLocked(-1, null, 0, bitmapContainingExtras); + } + + @GuardedBy("mLock") + void provideContextImageLocked(int taskId, @Nullable GraphicBuffer snapshot, + int colorSpaceIdForSnapshot, @NonNull Bundle imageContextRequestExtras) { RemoteContentSuggestionsService service = ensureRemoteServiceLocked(); if (service != null) { - GraphicBuffer snapshotBuffer = null; - int colorSpaceId = 0; - - // Skip taking TaskSnapshot when bitmap is provided. - if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) { - ActivityManager.TaskSnapshot snapshot = - mActivityTaskManagerInternal.getTaskSnapshotNoRestore(taskId, false); - if (snapshot != null) { - snapshotBuffer = snapshot.getSnapshot(); - ColorSpace colorSpace = snapshot.getColorSpace(); - if (colorSpace != null) { - colorSpaceId = colorSpace.getId(); - } - } - } - - service.provideContextImage(taskId, snapshotBuffer, colorSpaceId, + service.provideContextImage(taskId, snapshot, colorSpaceIdForSnapshot, imageContextRequestExtras); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ec84ae73d655..76a8e1474a95 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -40,6 +40,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.uidRulesToString; @@ -50,6 +51,7 @@ import static android.system.OsConstants.IPPROTO_UDP; import static java.util.Map.Entry; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; @@ -2702,10 +2704,18 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (msg.what) { case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: { - final NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj; + NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj; if (networkCapabilities.hasConnectivityManagedCapability()) { Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability."); } + if (networkCapabilities.hasTransport(TRANSPORT_TEST)) { + // Make sure the original object is not mutated. NetworkAgent normally + // makes a copy of the capabilities when sending the message through + // the Messenger, but if this ever changes, not making a defensive copy + // here will give attack vectors to clients using this code path. + networkCapabilities = new NetworkCapabilities(networkCapabilities); + networkCapabilities.restrictCapabilitesForTestNetwork(); + } updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities); break; } @@ -5778,7 +5788,16 @@ public class ConnectivityService extends IConnectivityManager.Stub public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) { - enforceNetworkFactoryPermission(); + if (networkCapabilities.hasTransport(TRANSPORT_TEST)) { + enforceAnyPermissionOf(Manifest.permission.MANAGE_TEST_NETWORKS); + // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in + // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never + // sees capabilities that may be malicious, which might prevent mistakes in the future. + networkCapabilities = new NetworkCapabilities(networkCapabilities); + networkCapabilities.restrictCapabilitesForTestNetwork(); + } else { + enforceNetworkFactoryPermission(); + } LinkProperties lp = new LinkProperties(linkProperties); lp.ensureDirectlyConnectedRoutes(); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 0bf81e0678dc..bb9446078da6 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -155,6 +155,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; import com.android.server.pm.Installer; +import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.storage.AppFuseBridge; import com.android.server.storage.StorageSessionController; import com.android.server.storage.StorageSessionController.ExternalStorageServiceException; @@ -3268,6 +3269,25 @@ class StorageManagerService extends IStorageManager.Stub } } + @Override + public void fixupAppDir(String path) { + final Matcher matcher = KNOWN_APP_DIR_PATHS.matcher(path); + if (matcher.matches()) { + AndroidPackage pkg = mPmInternal.getPackage(matcher.group(3)); + if (pkg != null) { + try { + mVold.fixupAppDir(path + "/", pkg.getUid()); + } catch (RemoteException | ServiceSpecificException e) { + Log.e(TAG, "Failed to fixup app dir for " + pkg.getPackageName(), e); + } + } else { + Log.e(TAG, "Can't find package belonging to " + path); + } + } else { + Log.e(TAG, "Path " + path + " is not a valid application-specific directory"); + } + } + /** Not thread safe */ class AppFuseMountScope extends AppFuseBridge.MountScope { private boolean mMounted = false; diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java index 8f914fe6f59f..94d6b135e554 100644 --- a/services/core/java/com/android/server/SystemServerInitThreadPool.java +++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java @@ -25,6 +25,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.Preconditions; import com.android.server.am.ActivityManagerService; +import com.android.server.utils.TimingsTraceAndSlog; import java.util.ArrayList; import java.util.List; @@ -93,6 +94,8 @@ public class SystemServerInitThreadPool { mPendingTasks.add(description); } return mService.submit(() -> { + TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog(); + traceLog.traceBegin("InitThreadPoolExec:" + description); if (IS_DEBUGGABLE) { Slog.d(TAG, "Started executing " + description); } @@ -100,6 +103,7 @@ public class SystemServerInitThreadPool { runnable.run(); } catch (RuntimeException e) { Slog.e(TAG, "Failure in " + description + ": " + e, e); + traceLog.traceEnd(); throw e; } synchronized (mPendingTasks) { @@ -108,6 +112,7 @@ public class SystemServerInitThreadPool { if (IS_DEBUGGABLE) { Slog.d(TAG, "Finished executing " + description); } + traceLog.traceEnd(); }); } @@ -132,7 +137,10 @@ public class SystemServerInitThreadPool { */ static void shutdown() { synchronized (LOCK) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("WaitInitThreadPoolShutdown"); if (sInstance == null) { + t.traceEnd(); Slog.wtf(TAG, "Already shutdown", new Exception()); return; } @@ -147,6 +155,7 @@ public class SystemServerInitThreadPool { } catch (InterruptedException e) { Thread.currentThread().interrupt(); dumpStackTraces(); + t.traceEnd(); throw new IllegalStateException(TAG + " init interrupted"); } if (!terminated) { @@ -160,11 +169,13 @@ public class SystemServerInitThreadPool { synchronized (sInstance.mPendingTasks) { copy.addAll(sInstance.mPendingTasks); } + t.traceEnd(); throw new IllegalStateException("Cannot shutdown. Unstarted tasks " + unstartedRunnables + " Unfinished tasks " + copy); } sInstance = null; // Make eligible for GC Slog.d(TAG, "Shutdown successful"); + t.traceEnd(); } } diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java index 03f4a54086a8..ba89fce0b3f8 100644 --- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java +++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java @@ -16,20 +16,15 @@ package com.android.server.am; -import static android.app.AppOpsManager.OP_NONE; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.app.Activity; import android.app.BroadcastOptions; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; -import android.os.BugreportManager; -import android.os.BugreportParams; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -115,17 +110,9 @@ public final class BugReportHandlerUtil { options.setBackgroundActivityStartsAllowed(true); final long identity = Binder.clearCallingIdentity(); try { - // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to - // let ResultBroadcastReceiver know the handler app is available. - context.sendOrderedBroadcastAsUser(intent, - UserHandle.of(handlerUser), + context.sendBroadcastAsUser(intent, UserHandle.of(handlerUser), android.Manifest.permission.DUMP, - OP_NONE, options.toBundle(), - new ResultBroadcastReceiver(), - /* scheduler= */ null, - Activity.RESULT_CANCELED, - /* initialData= */ null, - /* initialExtras= */ null); + options.toBundle()); } catch (RuntimeException e) { Slog.e(TAG, "Error while trying to launch bugreport handler app.", e); return false; @@ -189,19 +176,4 @@ public final class BugReportHandlerUtil { Binder.restoreCallingIdentity(identity); } } - - private static class ResultBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (getResultCode() == Activity.RESULT_OK) { - return; - } - - Slog.w(TAG, "Request bug report because handler app seems to be not available."); - BugreportManager bugreportManager = context.getSystemService(BugreportManager.class); - bugreportManager.requestBugreport( - new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE), - /* shareTitle= */null, /* shareDescription= */ null); - } - } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1f0146ad815d..ada3e4269f6a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4570,6 +4570,7 @@ public class AudioService extends IAudioService.Stub public void setWiredDeviceConnectionState(int type, @ConnectionState int state, String address, String name, String caller) { + enforceModifyAudioRoutingPermission(); if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { throw new IllegalArgumentException("Invalid state " + state); diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 58e332ab6d1d..c1fbcfba864a 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -653,9 +653,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); - mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context, - GnssLocationProvider.this::onNetworkAvailable, mLooper); - // App ops service to keep track of who is accessing the GPS mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -677,6 +674,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mNIHandler = new GpsNetInitiatedHandler(context, mNetInitiatedListener, mSuplEsEnabled); + mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context, + GnssLocationProvider.this::onNetworkAvailable, mLooper, mNIHandler); + sendMessage(INITIALIZE_HANDLER, 0, null); mGnssStatusListenerHelper = new GnssStatusListenerHelper(mContext, mHandler) { diff --git a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java index 93227bd78a81..5d6474bdbccc 100644 --- a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java @@ -29,12 +29,22 @@ import android.os.PowerManager; import android.provider.Telephony.Carriers; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.PreciseCallState; +import android.telephony.PhoneStateListener; import android.util.Log; +import com.android.internal.location.GpsNetInitiatedHandler; + import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; +import java.util.Map; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Iterator; /** * Handles network connection requests and network state change updates for AGPS data download. @@ -86,6 +96,9 @@ class GnssNetworkConnectivityHandler { private HashMap<Network, NetworkAttributes> mAvailableNetworkAttributes = new HashMap<>(HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS); + // Phone State Listeners to track all the active sub IDs + private HashMap<Integer, SubIdPhoneStateListener> mPhoneStateListeners; + private final ConnectivityManager mConnMgr; private final Handler mHandler; @@ -94,6 +107,9 @@ class GnssNetworkConnectivityHandler { private int mAGpsDataConnectionState; private InetAddress mAGpsDataConnectionIpAddr; private int mAGpsType; + private int mActiveSubId = -1; + private final GpsNetInitiatedHandler mNiHandler; + private final Context mContext; @@ -166,18 +182,109 @@ class GnssNetworkConnectivityHandler { GnssNetworkConnectivityHandler(Context context, GnssNetworkListener gnssNetworkListener, - Looper looper) { + Looper looper, + GpsNetInitiatedHandler niHandler) { mContext = context; mGnssNetworkListener = gnssNetworkListener; + SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); + if (subManager != null) { + subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); + } + PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); mHandler = new Handler(looper); + mNiHandler = niHandler; mConnMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); mSuplConnectivityCallback = createSuplConnectivityCallback(); } + /** + * SubId Phone State Listener is used cache the last active Sub ID when a call is made, + * which will be used during an emergency call to set the Network Specifier to the particular + * sub when an emergency supl connection is requested + */ + private final class SubIdPhoneStateListener extends PhoneStateListener { + private Integer mSubId; + SubIdPhoneStateListener(Integer subId) { + mSubId = subId; + } + @Override + public void onPreciseCallStateChanged(PreciseCallState state) { + if (state.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()) { + mActiveSubId = mSubId; + if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId); + } + } + }; + + /** + * Subscription Changed Listener is used to get all active subscriptions and create a + * Phone State Listener for each Sub ID that we find in the active subscription list + */ + private final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener + = new SubscriptionManager.OnSubscriptionsChangedListener() { + @Override + public void onSubscriptionsChanged() { + if (mPhoneStateListeners == null) { + // Capacity=2 Load-Factor=1.0, as typically no more than 2 SIMs + mPhoneStateListeners = new HashMap<Integer, SubIdPhoneStateListener>(2,1); + } + SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); + TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class); + if (subManager != null && telManager != null) { + List<SubscriptionInfo> subscriptionInfoList = + subManager.getActiveSubscriptionInfoList(); + HashSet<Integer> activeSubIds = new HashSet<Integer>(); + if (subscriptionInfoList != null) { + if (DEBUG) Log.d(TAG, "Active Sub List size: " + subscriptionInfoList.size()); + // populate phone state listeners with all new active subs + for (SubscriptionInfo subInfo : subscriptionInfoList) { + activeSubIds.add(subInfo.getSubscriptionId()); + if (!mPhoneStateListeners.containsKey(subInfo.getSubscriptionId())) { + TelephonyManager subIdTelManager = + telManager.createForSubscriptionId(subInfo.getSubscriptionId()); + if (subIdTelManager != null) { + if (DEBUG) Log.d(TAG, "Listener sub" + subInfo.getSubscriptionId()); + SubIdPhoneStateListener subIdPhoneStateListener = + new SubIdPhoneStateListener(subInfo.getSubscriptionId()); + mPhoneStateListeners.put(subInfo.getSubscriptionId(), + subIdPhoneStateListener); + subIdTelManager.listen(subIdPhoneStateListener, + PhoneStateListener.LISTEN_PRECISE_CALL_STATE); + } + } + } + } + // clean up phone state listeners than no longer have active subs + Iterator<Map.Entry<Integer, SubIdPhoneStateListener> > iterator = + mPhoneStateListeners.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<Integer, SubIdPhoneStateListener> element = iterator.next(); + if (!activeSubIds.contains(element.getKey())) { + TelephonyManager subIdTelManager = + telManager.createForSubscriptionId(element.getKey()); + if (subIdTelManager != null) { + if (DEBUG) Log.d(TAG, "unregister listener sub " + element.getKey()); + subIdTelManager.listen(element.getValue(), + PhoneStateListener.LISTEN_NONE); + // removes the element from mPhoneStateListeners + iterator.remove(); + } else { + Log.e(TAG, "Telephony Manager for Sub " + element.getKey() + " null"); + } + } + } + // clean up cached active phone call sub if it is no longer an active sub + if (!activeSubIds.contains(mActiveSubId)) { + mActiveSubId = -1; + } + } + } + }; + void registerNetworkCallbacks() { // register for connectivity change events. NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); @@ -467,6 +574,12 @@ class GnssNetworkConnectivityHandler { NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType)); networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + // During an emergency call, and when we have cached the Active Sub Id, we set the + // Network Specifier so that the network request goes to the correct Sub Id + if (mNiHandler.getInEmergency() && mActiveSubId >= 0) { + if (DEBUG) Log.d(TAG, "Adding Network Specifier: " + Integer.toString(mActiveSubId)); + networkRequestBuilder.setNetworkSpecifier(Integer.toString(mActiveSubId)); + } NetworkRequest networkRequest = networkRequestBuilder.build(); mConnMgr.requestNetwork( networkRequest, @@ -598,6 +711,15 @@ class GnssNetworkConnectivityHandler { } TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + // During an emergency call with an active sub id, get the Telephony Manager specific + // to the active sub to get the correct value from getServiceState and getNetworkType + if (mNiHandler.getInEmergency() && mActiveSubId >= 0) { + TelephonyManager subIdTelManager = + phone.createForSubscriptionId(mActiveSubId); + if (subIdTelManager != null) { + phone = subIdTelManager; + } + } ServiceState serviceState = phone.getServiceState(); String projection = null; String selection = null; diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java index 77921236e0d7..f2eb176aa676 100644 --- a/services/core/java/com/android/server/notification/BubbleExtractor.java +++ b/services/core/java/com/android/server/notification/BubbleExtractor.java @@ -157,8 +157,8 @@ public class BubbleExtractor implements NotificationSignalExtractor { } /** - * @return whether the user has enabled the provided notification to bubble, does not - * account for policy. + * @return whether the user has enabled the provided notification to bubble, and if the + * developer has provided valid information for the notification to bubble. */ @VisibleForTesting boolean canBubble(NotificationRecord r, String pkg, int userId) { @@ -184,8 +184,17 @@ public class BubbleExtractor implements NotificationSignalExtractor { } String shortcutId = metadata.getShortcutId(); - boolean shortcutValid = shortcutId != null - && mShortcutHelper.hasValidShortcutInfo(shortcutId, pkg, r.getUser()); + String notificationShortcutId = r.getShortcutInfo() != null + ? r.getShortcutInfo().getId() + : null; + boolean shortcutValid = false; + if (notificationShortcutId != null && shortcutId != null) { + // NoMan already checks validity of shortcut, just check if they match. + shortcutValid = shortcutId.equals(notificationShortcutId); + } else if (shortcutId != null) { + shortcutValid = + mShortcutHelper.getValidShortcutInfo(shortcutId, pkg, r.getUser()) != null; + } if (metadata.getIntent() == null && !shortcutValid) { // Should have a shortcut if intent is null logBubbleError(r.getKey(), diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 35dec5a59cec..7f805bef520d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -139,6 +139,7 @@ import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -387,10 +388,9 @@ public class NotificationManagerService extends SystemService { * still post toasts created with * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while * in the background. - * - * TODO(b/144152069): Add @EnabledAfter(Q) to target R+ after assessing impact on dogfood */ @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L; private IActivityManager mAm; @@ -2751,24 +2751,18 @@ public class NotificationManagerService extends SystemService { @Override public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @Nullable ITransientNotificationCallback callback) { - enqueueToast(pkg, token, text, null, duration, displayId, callback, false); + enqueueToast(pkg, token, text, null, duration, displayId, callback); } @Override public void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId) { - enqueueToast(pkg, token, null, callback, duration, displayId, null, true); - } - - @Override - public void enqueueTextOrCustomToast(String pkg, IBinder token, - ITransientNotification callback, int duration, int displayId, boolean isCustom) { - enqueueToast(pkg, token, null, callback, duration, displayId, null, isCustom); + enqueueToast(pkg, token, null, callback, duration, displayId, null); } private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId, - @Nullable ITransientNotificationCallback textCallback, boolean isCustom) { + @Nullable ITransientNotificationCallback textCallback) { if (DBG) { Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token + " duration=" + duration + " displayId=" + displayId); @@ -2807,11 +2801,15 @@ public class NotificationManagerService extends SystemService { } boolean isAppRenderedToast = (callback != null); - if (isAppRenderedToast && isCustom && !isSystemToast - && !isPackageInForegroundForToast(pkg, callingUid)) { + if (isAppRenderedToast && !isSystemToast && !isPackageInForegroundForToast(pkg, + callingUid)) { boolean block; long id = Binder.clearCallingIdentity(); try { + // CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK is gated on targetSdk, so block will be + // false for apps with targetSdk < R. For apps with targetSdk R+, text toasts + // are not app-rendered, so isAppRenderedToast == true means it's a custom + // toast. block = mPlatformCompat.isChangeEnabledByPackageName( CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, pkg, callingUser.getIdentifier()); @@ -2824,11 +2822,6 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(id); } if (block) { - // TODO(b/144152069): Remove informative toast - mUiHandler.post(() -> Toast.makeText(getContext(), - "Background custom toast blocked for package " + pkg + ".\n" - + "See g.co/dev/toast.", - Toast.LENGTH_SHORT).show()); Slog.w(TAG, "Blocking custom toast from package " + pkg + " due to package not in the foreground"); return; @@ -3453,7 +3446,7 @@ public class NotificationManagerService extends SystemService { ArrayList<ConversationChannelWrapper> conversations = mPreferencesHelper.getConversations(onlyImportant); for (ConversationChannelWrapper conversation : conversations) { - conversation.setShortcutInfo(mShortcutHelper.getShortcutInfo( + conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( conversation.getNotificationChannel().getConversationId(), conversation.getPkg(), UserHandle.of(UserHandle.getUserId(conversation.getUid())))); @@ -3476,7 +3469,7 @@ public class NotificationManagerService extends SystemService { ArrayList<ConversationChannelWrapper> conversations = mPreferencesHelper.getConversations(pkg, uid); for (ConversationChannelWrapper conversation : conversations) { - conversation.setShortcutInfo(mShortcutHelper.getShortcutInfo( + conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( conversation.getNotificationChannel().getConversationId(), pkg, UserHandle.of(UserHandle.getUserId(uid)))); @@ -3638,13 +3631,23 @@ public class NotificationManagerService extends SystemService { } /** + * @deprecated Use {@link #getActiveNotificationsWithAttribution(String, String)} instead. + */ + @Deprecated + @Override + public StatusBarNotification[] getActiveNotifications(String callingPkg) { + return getActiveNotificationsWithAttribution(callingPkg, null); + } + + /** * System-only API for getting a list of current (i.e. not cleared) notifications. * * Requires ACCESS_NOTIFICATIONS which is signature|system. * @returns A list of all the notifications, in natural order. */ @Override - public StatusBarNotification[] getActiveNotifications(String callingPkg) { + public StatusBarNotification[] getActiveNotificationsWithAttribution(String callingPkg, + String callingAttributionTag) { // enforce() will ensure the calling uid has the correct permission getContext().enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NOTIFICATIONS, @@ -3654,7 +3657,8 @@ public class NotificationManagerService extends SystemService { int uid = Binder.getCallingUid(); // noteOp will check to make sure the callingPkg matches the uid - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg) + if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg, + callingAttributionTag, null) == AppOpsManager.MODE_ALLOWED) { synchronized (mNotificationLock) { tmp = new StatusBarNotification[mNotificationList.size()]; @@ -3736,12 +3740,24 @@ public class NotificationManagerService extends SystemService { } /** - * System-only API for getting a list of recent (cleared, no longer shown) notifications. + * @deprecated Use {@link #getHistoricalNotificationsWithAttribution} instead. */ + @Deprecated @Override @RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS) public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count, boolean includeSnoozed) { + return getHistoricalNotificationsWithAttribution(callingPkg, null, count, + includeSnoozed); + } + + /** + * System-only API for getting a list of recent (cleared, no longer shown) notifications. + */ + @Override + @RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS) + public StatusBarNotification[] getHistoricalNotificationsWithAttribution(String callingPkg, + String callingAttributionTag, int count, boolean includeSnoozed) { // enforce() will ensure the calling uid has the correct permission getContext().enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NOTIFICATIONS, @@ -3751,7 +3767,8 @@ public class NotificationManagerService extends SystemService { int uid = Binder.getCallingUid(); // noteOp will check to make sure the callingPkg matches the uid - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg) + if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg, + callingAttributionTag, null) == AppOpsManager.MODE_ALLOWED) { synchronized (mArchive) { tmp = mArchive.getArray(count, includeSnoozed); @@ -3767,7 +3784,8 @@ public class NotificationManagerService extends SystemService { @Override @WorkerThread @RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS) - public NotificationHistory getNotificationHistory(String callingPkg) { + public NotificationHistory getNotificationHistory(String callingPkg, + String callingAttributionTag) { // enforce() will ensure the calling uid has the correct permission getContext().enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NOTIFICATIONS, @@ -3775,7 +3793,8 @@ public class NotificationManagerService extends SystemService { int uid = Binder.getCallingUid(); // noteOp will check to make sure the callingPkg matches the uid - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg) + if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg, + callingAttributionTag, null) == AppOpsManager.MODE_ALLOWED) { IntArray currentUserIds = mUserProfiles.getCurrentProfileIds(); Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryReadHistory"); @@ -5647,7 +5666,8 @@ public class NotificationManagerService extends SystemService { } } - r.setShortcutInfo(mShortcutHelper.getShortcutInfo(notification.getShortcutId(), pkg, user)); + r.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( + notification.getShortcutId(), pkg, user)); if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r, r.getSbn().getOverrideGroupKey() != null)) { diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java index 7bbb3b117517..f1ce3a7d9f48 100644 --- a/services/core/java/com/android/server/notification/ShortcutHelper.java +++ b/services/core/java/com/android/server/notification/ShortcutHelper.java @@ -121,7 +121,10 @@ class ShortcutHelper { mLauncherAppsService = launcherApps; } - ShortcutInfo getShortcutInfo(String shortcutId, String packageName, UserHandle user) { + /** + * Only returns shortcut info if it's found and if it's {@link ShortcutInfo#isLongLived()}. + */ + ShortcutInfo getValidShortcutInfo(String shortcutId, String packageName, UserHandle user) { if (mLauncherAppsService == null) { return null; } @@ -135,20 +138,15 @@ class ShortcutHelper { query.setShortcutIds(Arrays.asList(shortcutId)); query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED); List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user); - return shortcuts != null && shortcuts.size() > 0 + ShortcutInfo info = shortcuts != null && shortcuts.size() > 0 ? shortcuts.get(0) : null; + return info != null && info.isLongLived() ? info : null; } finally { Binder.restoreCallingIdentity(token); } } - boolean hasValidShortcutInfo(String shortcutId, String packageName, - UserHandle user) { - ShortcutInfo shortcutInfo = getShortcutInfo(shortcutId, packageName, user); - return shortcutInfo != null && shortcutInfo.isLongLived(); - } - /** * Shortcut based bubbles require some extra work to listen for shortcut changes. * diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b79f75a2d335..e605eeba4d98 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -161,6 +161,7 @@ import android.content.pm.DataLoaderType; import android.content.pm.FallbackCategoryProvider; import android.content.pm.FeatureInfo; import android.content.pm.IDexModuleRegisterCallback; +import android.content.pm.IPackageChangeObserver; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageDeleteObserver2; @@ -178,6 +179,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; import android.content.pm.ModuleInfo; +import android.content.pm.PackageChangeEvent; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; @@ -812,6 +814,10 @@ public class PackageManagerService extends IPackageManager.Stub private final OverlayConfig mOverlayConfig; + @GuardedBy("itself") + final private ArrayList<IPackageChangeObserver> mPackageChangeObservers = + new ArrayList<>(); + /** * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors. * @@ -5064,15 +5070,16 @@ public class PackageManagerService extends IPackageManager.Stub * action and a {@code android.intent.category.BROWSABLE} category</li> * </ul> */ - int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps) { + int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, + boolean matchSystemOnly) { return updateFlagsForResolve(flags, userId, callingUid, - wantInstantApps, false /*onlyExposedExplicitly*/); + wantInstantApps, matchSystemOnly, false /*onlyExposedExplicitly*/); } int updateFlagsForResolve(int flags, int userId, int callingUid, - boolean wantInstantApps, boolean onlyExposedExplicitly) { + boolean wantInstantApps, boolean onlyExposedExplicitly, boolean matchSystemOnly) { // Safe mode means we shouldn't match any third-party components - if (mSafeMode) { + if (mSafeMode || matchSystemOnly) { flags |= PackageManager.MATCH_SYSTEM_ONLY; } if (getInstantAppPackageName(callingUid) != null) { @@ -6164,7 +6171,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); - flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart); + flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); mPermissionManager.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, false /*checkShell*/, "resolve intent"); @@ -6196,7 +6204,8 @@ public class PackageManagerService extends IPackageManager.Stub intent = updateIntentForResolve(intent); final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver()); final int flags = updateFlagsForResolve( - 0, userId, callingUid, false /*includeInstantApps*/); + 0, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags, userId); synchronized (mLock) { @@ -6517,7 +6526,8 @@ public class PackageManagerService extends IPackageManager.Stub android.provider.Settings.Global.getInt(mContext.getContentResolver(), android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1; flags = updateFlagsForResolve( - flags, userId, callingUid, false /*includeInstantApps*/); + flags, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); intent = updateIntentForResolve(intent); // writer synchronized (mLock) { @@ -6729,7 +6739,8 @@ public class PackageManagerService extends IPackageManager.Stub } synchronized (mLock) { int flags = updateFlagsForResolve(0, parent.id, callingUid, - false /*includeInstantApps*/); + false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); CrossProfileDomainInfo xpDomainInfo = getCrossProfileDomainPreferredLpr( intent, resolvedType, flags, sourceUserId, parent.id); return xpDomainInfo != null; @@ -6815,7 +6826,8 @@ public class PackageManagerService extends IPackageManager.Stub } flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, - comp != null || pkgName != null /*onlyExposedExplicitly*/); + comp != null || pkgName != null /*onlyExposedExplicitly*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); if (comp != null) { final List<ResolveInfo> list = new ArrayList<>(1); final ActivityInfo ai = getActivityInfo(comp, flags, userId); @@ -7600,7 +7612,8 @@ public class PackageManagerService extends IPackageManager.Stub String resolvedType, int flags, int userId) { if (!mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); mPermissionManager.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, false /*checkShell*/, "query intent activity options"); @@ -7786,7 +7799,8 @@ public class PackageManagerService extends IPackageManager.Stub false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -7876,7 +7890,8 @@ public class PackageManagerService extends IPackageManager.Stub private ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags, int userId, int callingUid) { if (!mUserManager.exists(userId)) return null; - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + false /* matchSystemOnly */); List<ResolveInfo> query = queryIntentServicesInternal( intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/); if (query != null) { @@ -7907,7 +7922,8 @@ public class PackageManagerService extends IPackageManager.Stub false /*checkShell*/, "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); - flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps); + flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, + false /* matchSystemOnly */); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -8044,7 +8060,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); final String instantAppPkgName = getInstantAppPackageName(callingUid); - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + false /* matchSystemOnly */); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -16458,9 +16475,56 @@ public class PackageManagerService extends IPackageManager.Stub // BackgroundDexOptService will remove it from its blacklist. // TODO: Layering violation BackgroundDexOptService.notifyPackageChanged(packageName); + + notifyPackageChangeObserversOnUpdate(reconciledPkg); } } + private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) { + final PackageSetting pkgSetting = reconciledPkg.pkgSetting; + final PackageInstalledInfo pkgInstalledInfo = reconciledPkg.installResult; + final PackageRemovedInfo pkgRemovedInfo = pkgInstalledInfo.removedInfo; + + PackageChangeEvent pkgChangeEvent = new PackageChangeEvent(); + pkgChangeEvent.packageName = pkgSetting.pkg.getPackageName(); + pkgChangeEvent.version = pkgSetting.versionCode; + pkgChangeEvent.lastUpdateTimeMillis = pkgSetting.lastUpdateTime; + pkgChangeEvent.newInstalled = (pkgRemovedInfo == null || !pkgRemovedInfo.isUpdate); + pkgChangeEvent.dataRemoved = (pkgRemovedInfo != null && pkgRemovedInfo.dataRemoved); + pkgChangeEvent.isDeleted = false; + + notifyPackageChangeObservers(pkgChangeEvent); + } + + private void notifyPackageChangeObserversOnDelete(String packageName, long version) { + PackageChangeEvent pkgChangeEvent = new PackageChangeEvent(); + pkgChangeEvent.packageName = packageName; + pkgChangeEvent.version = version; + pkgChangeEvent.lastUpdateTimeMillis = 0L; + pkgChangeEvent.newInstalled = false; + pkgChangeEvent.dataRemoved = false; + pkgChangeEvent.isDeleted = true; + + notifyPackageChangeObservers(pkgChangeEvent); + } + + private void notifyPackageChangeObservers(PackageChangeEvent event) { + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "notifyPackageChangeObservers"); + synchronized (mPackageChangeObservers) { + for(IPackageChangeObserver observer : mPackageChangeObservers) { + try { + observer.onPackageChanged(event); + } catch(RemoteException e) { + Log.wtf(TAG, e); + } + } + } + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + /** * The set of data needed to successfully install the prepared package. This includes data that * will be used to scan and reconcile the package. @@ -17521,6 +17585,7 @@ public class PackageManagerService extends IPackageManager.Stub } catch (RemoteException e) { Log.i(TAG, "Observer no longer exists."); } //end catch + notifyPackageChangeObserversOnDelete(packageName, versionCode); }); } @@ -22980,8 +23045,49 @@ public class PackageManagerService extends IPackageManager.Stub } } + private final class PackageChangeObserverDeathRecipient implements IBinder.DeathRecipient { + private final IPackageChangeObserver mObserver; + + PackageChangeObserverDeathRecipient(IPackageChangeObserver observer) { + mObserver = observer; + } + + @Override + public void binderDied() { + synchronized (mPackageChangeObservers) { + mPackageChangeObservers.remove(mObserver); + Log.d(TAG, "Size of mPackageChangeObservers after removing dead observer is " + + mPackageChangeObservers.size()); + } + } + } + private class PackageManagerNative extends IPackageManagerNative.Stub { @Override + public void registerPackageChangeObserver(@NonNull IPackageChangeObserver observer) { + synchronized (mPackageChangeObservers) { + try { + observer.asBinder().linkToDeath( + new PackageChangeObserverDeathRecipient(observer), 0); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + mPackageChangeObservers.add(observer); + Log.d(TAG, "Size of mPackageChangeObservers after registry is " + + mPackageChangeObservers.size()); + } + } + + @Override + public void unregisterPackageChangeObserver(@NonNull IPackageChangeObserver observer) { + synchronized (mPackageChangeObservers) { + mPackageChangeObservers.remove(observer); + Log.d(TAG, "Size of mPackageChangeObservers after unregistry is " + + mPackageChangeObservers.size()); + } + } + + @Override public String[] getAllPackages() { return PackageManagerService.this.getAllPackages().toArray(new String[0]); } diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 161f30449a52..27288d852fb2 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManagerInternal; import android.permission.PermissionControllerManager; +import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; import android.util.ArrayMap; @@ -70,7 +72,9 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; /** @@ -180,8 +184,6 @@ public final class PermissionPolicyService extends SystemService { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addDataScheme("package"); - - /* TODO ntmyren: enable receiver when test flakes are fixed getContext().registerReceiverAsUser(new BroadcastReceiver() { final List<Integer> mUserSetupUids = new ArrayList<>(200); final Map<UserHandle, PermissionControllerManager> mPermControllerManagers = @@ -232,7 +234,6 @@ public final class PermissionPolicyService extends SystemService { manager.updateUserSensitiveForApp(uid); } }, UserHandle.ALL, intentFilter, null, null); - */ } /** diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index acf3f79e3626..74958b692eb2 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -3,6 +3,7 @@ { "name": "CtsBatterySavingTestCases", "options": [ + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.LargeTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] @@ -11,6 +12,7 @@ "name": "FrameworksMockingServicesTests", "options": [ {"include-filter": "com.android.server.power"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] }, @@ -18,6 +20,7 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.power"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, { "exclude-filter": "com.android.server.power.PowerManagerServiceTest#testWakefulnessAwake_ShouldWakeUpWhenPluggedIn" diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 63048f6b95b3..a9e8d3fc7a3a 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -34,6 +34,7 @@ import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.Status; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -139,6 +140,14 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware throw new ServiceSpecificException(((RecoverableException) e).errorCode, e.getMessage()); } + + /* Throwing an exception is not enough in this case. When the HAL behaves unexpectedly, the + system service and the HAL must be reset and the client must be notified. Without a full + reset in this catastrophic case, the state of the HAL and the system service cannot be + guaranteed to the client. + */ + Log.wtf(TAG, "Crashing system server due to unrecoverable exception", e); + Process.killProcess(Process.myPid()); throw new InternalServerError(e); } @@ -377,7 +386,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware mCallback = callback; mHandle = handle; try { - mCallback.asBinder().linkToDeath(null, 0); + mCallback.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -680,7 +689,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware try { mDelegate.detach(); mDelegate = null; - mCallback.asBinder().unlinkToDeath(null, 0); + mCallback.asBinder().unlinkToDeath(this, 0); mModules.get(mHandle).remove(this); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6d53786ddf41..3477d82f4d71 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4618,6 +4618,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } catch (Exception e) { Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e); } + // The activity may be waiting for stop, but that is no longer appropriate if we are + // starting the activity again + mStackSupervisor.mStoppingActivities.remove(this); } return false; } @@ -4667,7 +4670,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * and {@link #shouldPauseActivity(ActivityRecord)}. */ private boolean shouldStartActivity() { - return mVisibleRequested && isState(STOPPED); + return mVisibleRequested && (isState(STOPPED) || isState(STOPPING)); } /** diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index e8bfe8ef63ac..c6f375ea5d18 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -36,7 +36,6 @@ import static android.app.WindowConfiguration.windowingModeToString; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; @@ -303,9 +302,6 @@ class ActivityStack extends Task { private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; - // TODO(task-hierarchy): remove when tiles can be actual parents - TaskTile mTile = null; - private final Handler mHandler; private class ActivityStackHandler extends Handler { @@ -551,10 +547,10 @@ class ActivityStack extends Task { } ActivityStack(ActivityTaskManagerService atmService, int id, int activityType, - ActivityInfo info, Intent intent) { + ActivityInfo info, Intent intent, boolean createdByOrganizer) { this(atmService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, null /*taskDescription*/, null /*stack*/); - + mCreatedByOrganizer = createdByOrganizer; setActivityType(activityType); } @@ -601,20 +597,11 @@ class ActivityStack extends Task { } @Override - public void resolveTileOverrideConfiguration(Configuration newParentConfig) { - super.resolveTileOverrideConfiguration(newParentConfig); - if (mTile != null) { - // If this is a virtual child of a tile, simulate the parent-child relationship - mTile.updateResolvedConfig(getResolvedOverrideConfiguration()); - } - } - - @Override public void onConfigurationChanged(Configuration newParentConfig) { // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are // particularly for ActivityStack, like preventing bounds changes when inheriting certain // windowing mode. - if (!isRootTask() || this instanceof TaskTile) { + if (!isRootTask()) { super.onConfigurationChanged(newParentConfig); return; } @@ -689,6 +676,9 @@ class ActivityStack extends Task { @Override public void setWindowingMode(int windowingMode) { + // Reset the cached result of toString() + stringName = null; + // Calling Task#setWindowingMode() for leaf task since this is the a specialization of // {@link #setWindowingMode(int)} for ActivityStack. if (!isRootTask()) { @@ -742,7 +732,6 @@ class ActivityStack extends Task { final int currentOverrideMode = getRequestedOverrideWindowingMode(); final DisplayContent display = getDisplay(); final Task topTask = getTopMostTask(); - final ActivityStack splitScreenStack = display.getRootSplitScreenPrimaryTask(); int windowingMode = preferredWindowingMode; if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED && isTransientWindowingMode(currentMode)) { @@ -756,14 +745,14 @@ class ActivityStack extends Task { windowingMode = display.validateWindowingMode(windowingMode, null /* ActivityRecord */, topTask, getActivityType()); } - if (splitScreenStack == this + if (display.getRootSplitScreenPrimaryTask() == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { // Resolution to split-screen secondary for the primary split-screen stack means // we want to leave split-screen mode. windowingMode = mRestoreOverrideWindowingMode; } - final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryTask(); + final boolean alreadyInSplitScreenMode = display.isSplitScreenModeActivated(); // Don't send non-resizeable notifications if the windowing mode changed was a side effect // of us entering split-screen mode. @@ -831,7 +820,7 @@ class ActivityStack extends Task { return; } - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && splitScreenStack != null) { + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && alreadyInSplitScreenMode) { // We already have a split-screen stack in this display, so just move the tasks over. // TODO: Figure-out how to do all the stuff in // AMS.setTaskWindowingModeSplitScreenPrimary @@ -1063,7 +1052,7 @@ class ActivityStack extends Task { final DisplayContent display = getDisplay(); if (inSplitScreenSecondaryWindowingMode()) { - // If the stack is in split-screen seconardy mode, we need to make sure we move the + // If the stack is in split-screen secondary mode, we need to make sure we move the // primary split-screen stack forward in the case it is currently behind a fullscreen // stack so both halves of the split-screen appear on-top and the fullscreen stack isn't // cutting between them. @@ -1085,12 +1074,13 @@ class ActivityStack extends Task { display.moveHomeStackToFront(reason + " returnToHome"); } - final boolean movingTask = task != null; - display.positionStackAtTop(this, !movingTask /* includingParents */, reason); - if (movingTask) { - // This also moves the entire hierarchy branch to top, including parents - positionChildAtTop(task); + if (isRootTask()) { + display.positionStackAtTop(this, false /* includingParents */, reason); } + if (task == null) { + task = this; + } + task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */); } /** @@ -1116,12 +1106,6 @@ class ActivityStack extends Task { } } - @Override - boolean isFocusable() { - // Special check for tile which isn't really in the hierarchy - return mTile != null ? mTile.isFocusable() : super.isFocusable(); - } - boolean isTopActivityFocusable() { final ActivityRecord r = topRunningActivity(); return r != null ? r.isFocusable() @@ -3542,6 +3526,10 @@ class ActivityStack extends Task { @Override void onChildPositionChanged(WindowContainer child) { + if (isOrganized()) { + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */); + } + if (!mChildren.contains(child)) { return; } @@ -3572,11 +3560,6 @@ class ActivityStack extends Task { if (oldDisplay != null && oldDisplay.isRemoving()) { postReparent(); } - if (mTile != null && getSurfaceControl() != null) { - // by now, the TaskStack should already have been reparented, so we can reparent its - // surface here - reparentSurfaceControl(getPendingTransaction(), mTile.getSurfaceControl()); - } } void reparent(DisplayContent newParent, boolean onTop) { @@ -3614,16 +3597,7 @@ class ActivityStack extends Task { @Override void getRelativeDisplayedPosition(Point outPos) { - // check for tile which is "virtually" a parent. - if (mTile != null) { - final Rect dispBounds = getDisplayedBounds(); - outPos.set(dispBounds.left, dispBounds.top); - final Rect parentBounds = mTile.getBounds(); - outPos.offset(-parentBounds.left, -parentBounds.top); - } else { - super.getRelativeDisplayedPosition(outPos); - } - + super.getRelativeDisplayedPosition(outPos); final int outset = getStackOutset(); outPos.x -= outset; outPos.y -= outset; @@ -3633,16 +3607,6 @@ class ActivityStack extends Task { if (mSurfaceControl == null) { return; } - if (mTile != null) { - // Tile controls crop, so the app needs to be able to draw its background outside of - // the stack bounds for when the tile crop gets bigger than the stack. - if (mLastSurfaceSize.equals(0, 0)) { - return; - } - transaction.setWindowCrop(mSurfaceControl, null); - mLastSurfaceSize.set(0, 0); - return; - } final Rect stackBounds = getDisplayedBounds(); int width = stackBounds.width(); @@ -3666,9 +3630,6 @@ class ActivityStack extends Task { @Override void onDisplayChanged(DisplayContent dc) { - if (mTile != null && dc != mTile.getDisplay()) { - mTile.removeChild(this); - } super.onDisplayChanged(dc); if (isRootTask()) { updateSurfaceBounds(); @@ -3781,20 +3742,6 @@ class ActivityStack extends Task { return super.checkCompleteDeferredRemoval(); } - @Override - int getOrientation() { - return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET; - } - - private boolean canSpecifyOrientation() { - final int windowingMode = getWindowingMode(); - final int activityType = getActivityType(); - return windowingMode == WINDOWING_MODE_FULLSCREEN - || activityType == ACTIVITY_TYPE_HOME - || activityType == ACTIVITY_TYPE_RECENTS - || activityType == ACTIVITY_TYPE_ASSISTANT; - } - public DisplayInfo getDisplayInfo() { return mDisplayContent.getDisplayInfo(); } @@ -3825,49 +3772,6 @@ class ActivityStack extends Task { return shouldSleepActivities() || mAtmService.mShuttingDown; } - TaskTile getTile() { - return mTile; - } - - /** - * Don't call this directly. instead use {@link TaskTile#addChild} or - * {@link TaskTile#removeChild}. - */ - void setTile(TaskTile tile) { - TaskTile origTile = mTile; - mTile = tile; - final ConfigurationContainer parent = getParent(); - if (parent != null) { - onConfigurationChanged(parent.getConfiguration()); - } - - // Reparent to tile surface or back to original parent - if (getSurfaceControl() == null) { - return; - } - if (mTile != null) { - // don't use reparentSurfaceControl because we need to bypass taskorg check - mSurfaceAnimator.reparent(getPendingTransaction(), mTile.getSurfaceControl()); - } else if (mTile == null && origTile != null) { - mSurfaceAnimator.reparent(getPendingTransaction(), getParentSurfaceControl()); - } - } - - @Override - public SurfaceControl getParentSurfaceControl() { - // Tile is a "virtual" parent, so we need to intercept the parent surface here - return mTile != null ? mTile.getSurfaceControl() : super.getParentSurfaceControl(); - } - - @Override - void removeImmediately() { - // TODO(task-hierarchy): remove this override when tiles are in hierarchy - if (mTile != null) { - mTile.removeChild(this); - } - super.removeImmediately(); - } - @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { @@ -3912,7 +3816,7 @@ class ActivityStack extends Task { proto.write(SURFACE_HEIGHT, mSurfaceControl.getHeight()); } - proto.write(CREATED_BY_ORGANIZER, this instanceof TaskTile); + proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 57f357d384b5..4652f49f116c 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -423,6 +423,10 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final ActivityStack toStack = mToDisplay.getOrCreateStack( null, mTmpOptions, task, task.getActivityType(), mOnTop); + if (task == toStack) { + // The task was reused as the root task. + return; + } if (mOnTop) { final boolean isTopTask = task == mTopTask; @@ -1704,7 +1708,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mRootWindowContainer.getLaunchStack(null, aOptions, task, onTop); final WindowContainer parent = task.getParent(); - if (parent == stack) { + if (parent == stack || task == stack) { // Nothing else to do since it is already restored in the right stack. return true; } @@ -2237,7 +2241,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final boolean isSecondaryDisplayPreferred = (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY); final boolean inSplitScreenMode = actualStack != null - && actualStack.getDisplay().hasSplitScreenPrimaryTask(); + && actualStack.getDisplay().isSplitScreenModeActivated(); if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) { return; @@ -2284,16 +2288,14 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) { // Dismiss docked stack. If task appeared to be in docked stack but is not resizable - // we need to move it to top of fullscreen stack, otherwise it will be covered. - - final ActivityStack dockedStack = - task.getStack().getDisplay().getRootSplitScreenPrimaryTask(); - if (dockedStack != null) { + final DisplayContent display = task.getStack().getDisplay(); + if (display.isSplitScreenModeActivated()) { // Display a warning toast that we tried to put an app that doesn't support // split-screen in split-screen. mService.getTaskChangeNotificationController() .notifyActivityDismissingDockedStack(); - dockedStack.getDisplay().onSplitScreenModeDismissed(); - dockedStack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, + display.onSplitScreenModeDismissed(); + display.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, true /* notifyClients */); } return; @@ -2602,7 +2604,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { "startActivityFromRecents: Task " + taskId + " not found."); } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && task.getWindowingMode() != windowingMode) { - mService.moveTaskToSplitScreenPrimaryTile(task, true /* toTop */); + mService.moveTaskToSplitScreenPrimaryTask(task, true /* toTop */); } if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 14ca7cbae4b3..da1c045dba16 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -30,7 +30,6 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WaitResult.LAUNCH_STATE_COLD; import static android.app.WaitResult.LAUNCH_STATE_HOT; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -78,6 +77,7 @@ import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY; import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1566,7 +1566,7 @@ class ActivityStarter { } if (!mAvoidMoveToFront && mDoResume) { - mTargetStack.moveToFront("reuseOrNewTask"); + mTargetStack.getStack().moveToFront("reuseOrNewTask", targetTask); if (mOptions != null) { if (mPreferredWindowingMode != WINDOWING_MODE_UNDEFINED) { mTargetStack.setWindowingMode(mPreferredWindowingMode); @@ -2364,6 +2364,7 @@ class ActivityStarter { private void setTargetStackIfNeeded(ActivityRecord intentActivity) { mTargetStack = intentActivity.getRootTask(); mTargetStack.mLastPausedActivity = null; + Task intentTask = intentActivity.getTask(); // If the target task is not in the front, then we need to bring it to the front... // except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have // the same behavior as if a new instance was being started, which means not bringing it @@ -2374,7 +2375,7 @@ class ActivityStarter { final ActivityRecord curTop = (focusStack == null) ? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop); final Task topTask = curTop != null ? curTop.getTask() : null; - differentTopTask = topTask != intentActivity.getTask() + differentTopTask = topTask != intentTask || (focusStack != null && topTask != focusStack.getTopMostTask()); } else { // The existing task should always be different from those in other displays. @@ -2391,7 +2392,6 @@ class ActivityStarter { intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask()); } - final Task intentTask = intentActivity.getTask(); final ActivityStack launchStack = getLaunchStack(mStartActivity, mLaunchFlags, intentTask, mOptions); if (launchStack == null || launchStack == mTargetStack) { @@ -2400,6 +2400,14 @@ class ActivityStarter { // new intent has delivered. final boolean isSplitScreenTopStack = mTargetStack.isTopSplitScreenStack(); + // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels + // tasks hierarchies. + if (mTargetStack != intentTask + && mTargetStack != intentTask.getParent().asTask()) { + intentTask.getParent().positionChildAt(POSITION_TOP, intentTask, + false /* includingParents */); + intentTask = intentTask.getParent().asTask(); + } // We only want to move to the front, if we aren't going to launch on a // different stack. If we launch on a different stack, we will put the // task on top there. @@ -2420,8 +2428,8 @@ class ActivityStarter { // Need to update mTargetStack because if task was moved out of it, the original stack may // be destroyed. mTargetStack = intentActivity.getRootTask(); - mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(), - WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack); + mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED, + DEFAULT_DISPLAY, mTargetStack); } private void resumeTargetStackIfNeeded() { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index edc87e5a4d88..2263795f9442 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -548,8 +548,11 @@ public abstract class ActivityTaskManagerInternal { /** * Gets bitmap snapshot of the provided task id. + * + * <p>Warning! this may restore the snapshot from disk so can block, don't call in a latency + * sensitive environment. */ - public abstract ActivityManager.TaskSnapshot getTaskSnapshotNoRestore(int taskId, + public abstract ActivityManager.TaskSnapshot getTaskSnapshotBlocking(int taskId, boolean isLowResolution); /** Returns true if uid is considered foreground for activity start purposes. */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f7278e70cede..a5b0026398b6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -31,12 +31,12 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -122,6 +122,7 @@ import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_ import static com.android.server.wm.Task.LOCK_TASK_AUTH_DONT_LOCK; import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.Manifest; import android.annotation.IntDef; @@ -144,7 +145,6 @@ import android.app.IApplicationThread; import android.app.IAssistDataReceiver; import android.app.INotificationManager; import android.app.IRequestFinishCallback; -import android.window.ITaskOrganizerController; import android.app.ITaskStackListener; import android.app.Notification; import android.app.NotificationManager; @@ -230,9 +230,9 @@ import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; +import android.view.WindowManager; import android.window.IWindowOrganizerController; import android.window.WindowContainerTransaction; -import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -1274,9 +1274,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); + try { getActivityStartController().obtainStarter(intent, "dream") .setActivityInfo(a) + .setActivityOptions(options.toBundle()) .setIsDream(true) .execute(); return true; @@ -2349,16 +2353,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final ActivityStack stack = task.getStack(); - // Convert some windowing-mode changes into root-task reparents for split-screen. - if (stack.getTile() != null) { - stack.getDisplay().onSplitScreenModeDismissed(); - } if (toTop) { stack.moveToFront("setTaskWindowingMode", task); } - stack.setWindowingMode(windowingMode); - stack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, - true /* notifyClients */); + // Convert some windowing-mode changes into root-task reparents for split-screen. + if (stack.inSplitScreenWindowingMode()) { + stack.getDisplay().onSplitScreenModeDismissed(); + + } else { + stack.setWindowingMode(windowingMode); + stack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, + true /* notifyClients */); + } return true; } finally { Binder.restoreCallingIdentity(ident); @@ -2755,24 +2761,22 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final int prevMode = task.getWindowingMode(); - moveTaskToSplitScreenPrimaryTile(task, toTop); + moveTaskToSplitScreenPrimaryTask(task, toTop); return prevMode != task.getWindowingMode(); } - void moveTaskToSplitScreenPrimaryTile(Task task, boolean toTop) { - ActivityStack stack = task.getStack(); - TaskTile tile = null; - for (int i = stack.getDisplay().getStackCount() - 1; i >= 0; --i) { - tile = stack.getDisplay().getStackAt(i).asTile(); - if (tile != null && tile.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - break; - } + void moveTaskToSplitScreenPrimaryTask(Task task, boolean toTop) { + final DisplayContent display = task.getDisplayContent(); + final ActivityStack primarySplitTask = display.getRootSplitScreenPrimaryTask(); + if (primarySplitTask == null) { + throw new IllegalStateException("Can't enter split without associated organized task"); } - if (tile == null) { - throw new IllegalStateException("Can't enter split without associated tile"); + + if (toTop) { + display.positionStackAt(POSITION_TOP, primarySplitTask, false /* includingParents */); } WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken, tile.mRemoteToken, toTop); + wct.reparent(task.getStack().mRemoteToken, primarySplitTask.mRemoteToken, toTop); mWindowOrganizerController.applyTransaction(wct); } @@ -3239,7 +3243,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityStack stack = r.getRootTask(); final Task task = stack.getDisplay().createStack(stack.getWindowingMode(), - stack.getActivityType(), !ON_TOP, ainfo, intent); + stack.getActivityType(), !ON_TOP, ainfo, intent, + false /* createdByOrganizer */); if (!mRecentTasks.addToBottom(task)) { // The app has too many tasks already and we can't add any more @@ -4278,19 +4283,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { try { synchronized (mGlobalLock) { final DisplayContent dc = mRootWindowContainer.getDefaultDisplay(); - TaskTile primary = null; - TaskTile secondary = null; - for (int i = dc.getStackCount() - 1; i >= 0; --i) { - final TaskTile t = dc.getStackAt(i).asTile(); - if (t == null) { - continue; - } - if (t.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - primary = t; - } else if (t.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - secondary = t; - } - } + final Task primary = dc.getRootSplitScreenPrimaryTask(); + final Task secondary = dc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask() + && t.inSplitScreenSecondaryWindowingMode()); if (primary == null || secondary == null) { return; } @@ -7477,10 +7472,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public ActivityManager.TaskSnapshot getTaskSnapshotNoRestore(int taskId, - boolean isLowResolution) { + public ActivityManager.TaskSnapshot getTaskSnapshotBlocking( + int taskId, boolean isLowResolution) { return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution, - false /* restoreFromDisk */); + true /* restoreFromDisk */); } @Override diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 33dd9cf4ee05..1036af67c0db 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -467,6 +468,10 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return getActivityType() == ACTIVITY_TYPE_ASSISTANT; } + public boolean isActivityTypeDream() { + return getActivityType() == ACTIVITY_TYPE_DREAM; + } + public boolean isActivityTypeStandard() { return getActivityType() == ACTIVITY_TYPE_STANDARD; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7df731b3efab..ede456949116 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -200,7 +200,6 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; -import android.window.ITaskOrganizer; import android.view.IWindow; import android.view.InputChannel; import android.view.InputDevice; @@ -217,6 +216,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; +import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -456,6 +456,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final Configuration mTmpConfiguration = new Configuration(); + private ArrayList<Task> mTmpTasks = new ArrayList<>(); + /** Remove this display when animation on it has completed. */ private boolean mDeferredRemoval; @@ -654,8 +656,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final RootWindowContainer.FindTaskResult mTmpFindTaskResult = new RootWindowContainer.FindTaskResult(); - // When non-null, new stacks get put into this tile. - TaskTile mLaunchTile = null; + // When non-null, new tasks get put into this root task. + Task mLaunchRootTask = null; private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; @@ -2128,12 +2130,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } /** @return The primary split-screen task, and {@code null} otherwise. */ - ActivityStack getRootSplitScreenPrimaryTask() { + @Nullable ActivityStack getRootSplitScreenPrimaryTask() { return mTaskContainers.getRootSplitScreenPrimaryTask(); } - boolean hasSplitScreenPrimaryTask() { - return getRootSplitScreenPrimaryTask() != null; + boolean isSplitScreenModeActivated() { + Task task = getRootSplitScreenPrimaryTask(); + return task != null && task.hasChild(); } ActivityStack getRootPinnedTask() { @@ -2600,7 +2603,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } amendWindowTapExcludeRegion(mTouchExcludeRegion); // TODO(multi-display): Support docked stacks on secondary displays. - if (mDisplayId == DEFAULT_DISPLAY && getRootSplitScreenPrimaryTask() != null) { + if (mDisplayId == DEFAULT_DISPLAY && isSplitScreenModeActivated()) { mDividerControllerLocked.getTouchRegion(mTmpRect); mTmpRegion.set(mTmpRect); mTouchExcludeRegion.op(mTmpRegion, Op.UNION); @@ -2623,8 +2626,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If the task is home stack and it is resizable and visible (top of its root task), we want // to exclude the docked stack from touch so we need the entire screen area and not just a // small portion which the home stack currently is resized to. - if (task.isActivityTypeHome() && task.isVisible() && task.getStack().getTile() != null - && task.isResizeable()) { + if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) { mDisplayContent.getBounds(mTmpRect); } else { task.getDimBounds(mTmpRect); @@ -4328,15 +4330,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @VisibleForTesting ActivityStack getTopStack() { - // TODO(task-hierarchy): Just grab index -1 once tiles are in hierarchy. - for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) { - final ActivityStack child = mTaskContainers.getChildAt(i); - if (child instanceof TaskTile) { - continue; - } - return child; - } - return null; + final int count = mTaskContainers.getChildCount(); + return count > 0 ? mTaskContainers.getChildAt(count - 1) : null; } int getIndexOf(ActivityStack stack) { @@ -4375,10 +4370,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private void addStackReferenceIfNeeded(ActivityStack stack) { - // TODO(task-hierarchy): Remove when tiles are in hierarchy. - if (stack instanceof TaskTile) { - return; - } if (stack.isActivityTypeHome()) { if (mRootHomeTask != null) { if (!stack.isDescendantOf(mRootHomeTask)) { @@ -4390,27 +4381,26 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mRootHomeTask = stack; } } + + if (!stack.isRootTask()) { + return; + } final int windowingMode = stack.getWindowingMode(); if (windowingMode == WINDOWING_MODE_PINNED) { if (mRootPinnedTask != null) { - if (!stack.isDescendantOf(mRootPinnedTask)) { - throw new IllegalArgumentException( - "addStackReferenceIfNeeded: pinned stack=" + mRootPinnedTask - + " already exist on display=" + this + " stack=" + stack); - } - } else { - mRootPinnedTask = stack; + throw new IllegalArgumentException( + "addStackReferenceIfNeeded: pinned stack=" + mRootPinnedTask + + " already exist on display=" + this + " stack=" + stack); } + mRootPinnedTask = stack; } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { if (mRootSplitScreenPrimaryTask != null) { - if (!stack.isDescendantOf(mRootSplitScreenPrimaryTask)) { - throw new IllegalArgumentException("addStackReferenceIfNeeded:" - + " split-screen-primary" + " stack=" + mRootSplitScreenPrimaryTask - + " already exist on display=" + this + " stack=" + stack); - } - } else { - mRootSplitScreenPrimaryTask = stack; + throw new IllegalArgumentException( + "addStackReferenceIfNeeded: split screen primary stack=" + + mRootSplitScreenPrimaryTask + + " already exist on display=" + this + " stack=" + stack); } + mRootSplitScreenPrimaryTask = stack; } } @@ -4495,6 +4485,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ private int findPositionForStack(int requestedPosition, ActivityStack stack, boolean adding) { + if (stack.isActivityTypeDream()) { + return POSITION_TOP; + } + if (stack.inPinnedWindowingMode()) { return POSITION_TOP; } @@ -4645,10 +4639,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Apps and their containers are not allowed to specify an orientation while using // root tasks...except for the home stack if it is not resizable and currently // visible (top of) its root task. - if (mRootHomeTask != null && mRootHomeTask.isVisible() - && mRootHomeTask.getTile() != null) { + if (mRootHomeTask != null && mRootHomeTask.isVisible()) { final Task topMost = mRootHomeTask.getTopMostTask(); - final boolean resizable = topMost == null && topMost.isResizeable(); + final boolean resizable = topMost != null && topMost.isResizeable(); if (!(resizable && mRootHomeTask.matchParentBounds())) { final int orientation = mRootHomeTask.getOrientation(); if (orientation != SCREEN_ORIENTATION_UNSET) { @@ -4792,17 +4785,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mSplitScreenDividerAnchor = null; } } - - @Override - void onChildPositionChanged(WindowContainer child) { - // TODO(task-hierarchy): Move functionality to TaskTile when it's a proper parent. - TaskTile tile = ((ActivityStack) child).getTile(); - if (tile == null) { - return; - } - mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( - tile, false /* force */); - } } private class WindowContainers extends DisplayChildWindowContainer<WindowContainer> { @@ -4983,7 +4965,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private boolean skipImeWindowsDuringTraversal(DisplayContent dc) { // We skip IME windows so they're processed just above their target, except // in split-screen mode where we process the IME containers above the docked divider. - return dc.mInputMethodTarget != null && !dc.hasSplitScreenPrimaryTask(); + return dc.mInputMethodTarget != null && !dc.isSplitScreenModeActivated(); } /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ @@ -5657,6 +5639,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mAtmService.updateSleepIfNeededLocked(); } + void addStackReferenceIfNeeded(ActivityStack stack) { + mTaskContainers.addStackReferenceIfNeeded(stack); + } + + void removeStackReferenceIfNeeded(ActivityStack stack) { + mTaskContainers.removeStackReferenceIfNeeded(stack); + } + void onStackRemoved(ActivityStack stack) { if (ActivityTaskManagerDebugConfig.DEBUG_STACK) { Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId); @@ -5703,6 +5693,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo "positionStackAt: Can only have one task on display=" + this); } + final boolean movingToTop = wasContained && position >= getStackCount() - 1; + // Reset mPreferredTopFocusableStack before positioning to top or {@link + // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top + // resumed activity. + if (movingToTop && stack.isFocusable()) { + mPreferredTopFocusableStack = null; + } + // Since positionChildAt() is called during the creation process of pinned stacks, // ActivityStack#getStack() can be null. positionStackAt(position, stack, includingParents); @@ -5712,7 +5710,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // we are looking for top focusable stack. The condition {@code wasContained} restricts the // preferred stack is set only when moving an existing stack to top instead of adding a new // stack that may be too early (e.g. in the middle of launching or reparenting). - if (wasContained && position >= getStackCount() - 1 && stack.isFocusableAndVisible()) { + if (movingToTop && stack.isFocusableAndVisible()) { mPreferredTopFocusableStack = stack; } else if (mPreferredTopFocusableStack == stack) { mPreferredTopFocusableStack = null; @@ -5755,18 +5753,54 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** * Returns an existing stack compatible with the windowing mode and activity type or creates one * if a compatible stack doesn't exist. + * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean) + */ + ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) { + return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */, + null /* candidateTask */, false /* createdByOrganizer */); + } + + /** + * When two level tasks are required for given windowing mode and activity type, returns an + * existing compatible root task or creates a new one. + * For one level task, the candidate task would be reused to also be the root task or create + * a new root task if no candidate task. * @see #getStack(int, int) * @see #createStack(int, int, boolean) */ - ActivityStack getOrCreateStack(int windowingMode, int activityType, - boolean onTop) { + ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop, + Intent intent, Task candidateTask, boolean createdByOrganizer) { if (!alwaysCreateStack(windowingMode, activityType)) { ActivityStack stack = getStack(windowingMode, activityType); if (stack != null) { return stack; } + } else if (candidateTask != null) { + final ActivityStack stack = (ActivityStack) candidateTask; + final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; + if (isSplitScreenModeActivated()) { + final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask() + && t.inSplitScreenSecondaryWindowingMode()); + if (stack.getParent() == null) { + splitRootSecondary.addChild(stack, position); + } else if (stack.getParent() != splitRootSecondary) { + stack.reparent(splitRootSecondary, position); + } + } else if (stack.getDisplay() != this || !stack.isRootTask()) { + if (stack.getParent() == null) { + addStack(stack, position); + } else { + stack.reparent(this, onTop); + } + } + // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen. + if (candidateTask.getWindowingMode() != windowingMode) { + candidateTask.setWindowingMode(windowingMode); + } + return stack; } - return createStack(windowingMode, activityType, onTop); + return createStack(windowingMode, activityType, onTop, null /*info*/, intent, + createdByOrganizer); } /** @@ -5784,7 +5818,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // UNDEFINED windowing mode is a valid result and means that the new stack will inherit // it's display's windowing mode. windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType); - return getOrCreateStack(windowingMode, activityType, onTop); + return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */, + candidateTask, false /* createdByOrganizer */); } @VisibleForTesting @@ -5793,7 +5828,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } ActivityStack createStack(int windowingMode, int activityType, boolean onTop) { - return createStack(windowingMode, activityType, onTop, null /*info*/, null /*intent*/); + return createStack(windowingMode, activityType, onTop, null /* info */, null /* intent */, + false /* createdByOrganizer */); } /** @@ -5805,25 +5841,29 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}. * @param onTop If true the stack will be created at the top of the display, else at the bottom. + * @param info The started activity info. + * @param intent The intent that started this task. + * @param createdByOrganizer @{code true} if this is created by task organizer, @{code false} + * otherwise. * @return The newly created stack. */ ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info, - Intent intent) { + Intent intent, boolean createdByOrganizer) { if (mSingleTaskInstance && getStackCount() > 0) { // Create stack on default display instead since this display can only contain 1 stack. // TODO: Kinda a hack, but better that having the decision at each call point. Hoping // this goes away once ActivityView is no longer using virtual displays. return mRootWindowContainer.getDefaultDisplay().createStack( - windowingMode, activityType, onTop, info, intent); + windowingMode, activityType, onTop, info, intent, createdByOrganizer); } - if (activityType == ACTIVITY_TYPE_UNDEFINED) { + if (activityType == ACTIVITY_TYPE_UNDEFINED && !createdByOrganizer) { // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants - // anything else should be passing it in anyways... + // anything else should be passing it in anyways...except for the task organizer. activityType = ACTIVITY_TYPE_STANDARD; } - if (activityType != ACTIVITY_TYPE_STANDARD) { + if (activityType != ACTIVITY_TYPE_STANDARD && activityType != ACTIVITY_TYPE_UNDEFINED) { // For now there can be only one stack of a particular non-standard activity type on a // display. So, get that ignoring whatever windowing mode it is currently in. ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType); @@ -5842,39 +5882,39 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } final int stackId = getNextStackId(); - return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent); + return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent, + createdByOrganizer); } - /** @return the tile to create the next stack in. */ - private TaskTile updateLaunchTile(int windowingMode) { + /** @return the root task to create the next task in. */ + private Task updateLaunchRootTask(int windowingMode) { if (!isSplitScreenWindowingMode(windowingMode)) { - // Only split-screen windowing modes interact with tiles. + // Only split-screen windowing modes can do this currently... return null; } for (int i = getStackCount() - 1; i >= 0; --i) { - final TaskTile t = getStackAt(i).asTile(); - if (t == null || t.getRequestedOverrideWindowingMode() != windowingMode) { + final Task t = getStackAt(i); + if (!t.mCreatedByOrganizer || t.getRequestedOverrideWindowingMode() != windowingMode) { continue; } - // If not already set, pick a launch tile which is not the one we are launching - // into. - if (mLaunchTile == null) { + // If not already set, pick a launch root which is not the one we are launching into. + if (mLaunchRootTask == null) { for (int j = 0, n = getStackCount(); j < n; ++j) { - TaskTile tt = getStackAt(j).asTile(); - if (tt != t) { - mLaunchTile = tt; + final Task tt = getStackAt(j); + if (tt.mCreatedByOrganizer && tt != t) { + mLaunchRootTask = tt; break; } } } return t; } - return mLaunchTile; + return mLaunchRootTask; } @VisibleForTesting - ActivityStack createStackUnchecked(int windowingMode, int activityType, - int stackId, boolean onTop, ActivityInfo info, Intent intent) { + ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId, + boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) { if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) { throw new IllegalArgumentException("Stack with windowing mode cannot with non standard " + "activity type."); @@ -5884,19 +5924,25 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo info.applicationInfo = new ApplicationInfo(); } - TaskTile tile = updateLaunchTile(windowingMode); - if (tile != null) { - // Since this stack will be put into a tile, its windowingMode will be inherited. + // Task created by organizer are added as root. + Task launchRootTask = createdByOrganizer ? null : updateLaunchRootTask(windowingMode); + if (launchRootTask != null) { + // Since this stack will be put into a root task, its windowingMode will be inherited. windowingMode = WINDOWING_MODE_UNDEFINED; } + final ActivityStack stack = (ActivityStack) Task.create(mAtmService, stackId, activityType, - info, intent); - addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); - stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - true /* creating */); - if (tile != null) { - tile.addChild(stack, 0 /* index */); + info, intent, createdByOrganizer); + if (launchRootTask != null) { + launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); + if (onTop) { + positionStackAtTop((ActivityStack) launchRootTask, false /* includingParents */); + } + } else { + addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); + stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, + false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, + true /* creating */); } return stack; } @@ -6031,7 +6077,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTmpFindTaskResult.clear(); for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = getStackAt(stackNdx); - if (!r.hasCompatibleActivityType(stack)) { + if (!r.hasCompatibleActivityType(stack) && stack.isLeafTask()) { if (DEBUG_TASKS) { Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack); } @@ -6103,7 +6149,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int activityType = activityTypes[j]; for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack stack = getStackAt(i); - if (stack.getActivityType() == activityType) { + // Collect the root tasks that are currently being organized. + if (stack.isOrganized()) { + for (int k = stack.getChildCount() - 1; k >= 0; --k) { + final ActivityStack childStack = (ActivityStack) stack.getChildAt(k); + if (childStack.getActivityType() == activityType) { + stacks.add(childStack); + } + } + } else if (stack.getActivityType() == activityType) { stacks.add(stack); } } @@ -6117,13 +6171,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo void onSplitScreenModeDismissed() { mAtmService.deferWindowLayout(); try { - mLaunchTile = null; - for (int i = getStackCount() - 1; i >= 0; --i) { - final TaskTile t = getStackAt(i).asTile(); - if (t != null) { - t.removeAllChildren(); - } - } + mLaunchRootTask = null; + moveSplitScreenTasksToFullScreen(); } finally { final ActivityStack topFullscreenStack = getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -6141,6 +6190,24 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + private void moveSplitScreenTasksToFullScreen() { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mTmpTasks.clear(); + forAllTasks(task -> { + if (task.mCreatedByOrganizer && task.inSplitScreenWindowingMode() && task.hasChild()) { + mTmpTasks.add(task); + } + }); + + for (int i = mTmpTasks.size() - 1; i >= 0; i--) { + final Task root = mTmpTasks.get(i); + for (int j = 0; j < root.getChildCount(); j++) { + wct.reparent(root.getChildAt(j).mRemoteToken, null, true /* toTop */); + } + } + mAtmService.mWindowOrganizerController.applyTransaction(wct); + } + /** * Returns true if the {@param windowingMode} is supported based on other parameters passed in. * @param windowingMode The windowing mode we are checking support for. @@ -6253,7 +6320,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - final boolean inSplitScreenMode = hasSplitScreenPrimaryTask(); + final boolean inSplitScreenMode = isSplitScreenModeActivated(); if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { // Switch to the display's windowing mode if we are not in split-screen mode and we are @@ -6278,10 +6345,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } boolean isTopNotPinnedStack(ActivityStack stack) { - // TODO(task-hierarchy): Remove when tiles are in hierarchy. - if (stack instanceof TaskTile) { - return false; - } for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack current = getStackAt(i); if (!current.inPinnedWindowingMode()) { @@ -6503,7 +6566,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If default display is in split-window mode, set windowing mode of the stack // to split-screen secondary. Otherwise, set the windowing mode to undefined by // default to let stack inherited the windowing mode from the new display. - final int windowingMode = toDisplay.hasSplitScreenPrimaryTask() + final int windowingMode = toDisplay.isSplitScreenModeActivated() ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_UNDEFINED; stack.reparent(toDisplay, true /* onTop */); @@ -6607,27 +6670,35 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * already top-most. */ ActivityStack getStackAbove(ActivityStack stack) { - final int stackIndex = getIndexOf(stack) + 1; - return (stackIndex < getStackCount()) ? getStackAt(stackIndex) : null; + final WindowContainer wc = stack.getParent(); + final int index = wc.mChildren.indexOf(stack) + 1; + return (index < wc.mChildren.size()) ? (ActivityStack) wc.mChildren.get(index) : null; } /** * Adjusts the {@param stack} behind the last visible stack in the display if necessary. * Generally used in conjunction with {@link #moveStackBehindStack}. */ + // TODO(b/151575894): Remove special stack movement methods. void moveStackBehindBottomMostVisibleStack(ActivityStack stack) { if (stack.shouldBeVisible(null)) { // Skip if the stack is already visible return; } - // Move the stack to the bottom to not affect the following visibility checks - positionStackAtBottom(stack); + final boolean isRootTask = stack.isRootTask(); + if (isRootTask) { + // Move the stack to the bottom to not affect the following visibility checks + positionStackAtBottom(stack); + } else { + stack.getParent().positionChildAt(POSITION_BOTTOM, stack, false /* includingParents */); + } // Find the next position where the stack should be placed - final int numStacks = getStackCount(); + final int numStacks = isRootTask ? getStackCount() : stack.getParent().getChildCount(); for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { - final ActivityStack s = getStackAt(stackNdx); + final ActivityStack s = isRootTask ? getStackAt(stackNdx) + : (ActivityStack) stack.getParent().getChildAt(stackNdx); if (s == stack) { continue; } @@ -6636,7 +6707,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; if (s.shouldBeVisible(null) && isValidWindowingMode) { // Move the provided stack to behind this stack - positionStackAt(stack, Math.max(0, stackNdx - 1)); + final int position = Math.max(0, stackNdx - 1); + if (isRootTask) { + positionStackAt(stack, position); + } else { + stack.getParent().positionChildAt(position, stack, false /*includingParents */); + } break; } } @@ -6652,15 +6728,25 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return; } + final WindowContainer parent = stack.getParent(); + if (parent == null || parent != behindStack.getParent()) { + return; + } + // Note that positionChildAt will first remove the given stack before inserting into the // list, so we need to adjust the insertion index to account for the removed index // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the // position internally - final int stackIndex = getIndexOf(stack); - final int behindStackIndex = getIndexOf(behindStack); + final int stackIndex = parent.mChildren.indexOf(stack); + final int behindStackIndex = parent.mChildren.indexOf(behindStack); final int insertIndex = stackIndex <= behindStackIndex ? behindStackIndex - 1 : behindStackIndex; - positionStackAt(stack, Math.max(0, insertIndex)); + final int position = Math.max(0, insertIndex); + if (stack.isRootTask()) { + positionStackAt(stack, position); + } else { + parent.positionChildAt(position, stack, false /* includingParents */); + } } void ensureActivitiesVisible(ActivityRecord starting, int configChanges, @@ -6697,19 +6783,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return getHomeActivityForUser(mRootWindowContainer.mCurrentUser); } - // TODO(task-hierarchy): Remove when tiles are in hierarchy. - void addTile(TaskTile tile) { - mTaskContainers.addChild(tile, POSITION_BOTTOM); - ITaskOrganizer organizer = mAtmService.mTaskOrganizerController.getTaskOrganizer( - tile.getWindowingMode()); - tile.setTaskOrganizer(organizer); - } - - // TODO(task-hierarchy): Remove when tiles are in hierarchy. - void removeTile(TaskTile tile) { - mTaskContainers.removeChild(tile); - } - @Nullable ActivityRecord getHomeActivityForUser(int userId) { final ActivityStack homeStack = getRootHomeTask(); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 5e88fb0437c6..4da4a79cfd83 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1527,25 +1527,7 @@ public class DisplayPolicy { && (mNotificationShade.getAttrs().privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0; - // When the navigation bar isn't visible, we put up a fake input window to catch all - // touch events. This way we can detect when the user presses anywhere to bring back the - // nav bar and ensure the application doesn't see the event. - if (navVisible || navAllowedHidden) { - if (mInputConsumer != null) { - mInputConsumer.dismiss(); - mHandler.sendMessage( - mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer)); - mInputConsumer = null; - } - } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) { - mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer( - mHandler.getLooper(), - INPUT_CONSUMER_NAVIGATION, - HideNavInputEventReceiver::new); - // As long as mInputConsumer is active, hover events are not dispatched to the app - // and the pointer icon is likely to become stale. Hide it to avoid confusion. - InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL); - } + updateHideNavInputEventReceiver(navVisible, navAllowedHidden); // For purposes of positioning and showing the nav bar, if we have decided that it can't // be hidden (because of the screen aspect ratio), then take that into account. @@ -1567,6 +1549,28 @@ public class DisplayPolicy { mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation; } + void updateHideNavInputEventReceiver(boolean navVisible, boolean navAllowedHidden) { + // When the navigation bar isn't visible, we put up a fake input window to catch all + // touch events. This way we can detect when the user presses anywhere to bring back the + // nav bar and ensure the application doesn't see the event. + if (navVisible || navAllowedHidden) { + if (mInputConsumer != null) { + mInputConsumer.dismiss(); + mHandler.sendMessage( + mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer)); + mInputConsumer = null; + } + } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) { + mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer( + mHandler.getLooper(), + INPUT_CONSUMER_NAVIGATION, + HideNavInputEventReceiver::new); + // As long as mInputConsumer is active, hover events are not dispatched to the app + // and the pointer icon is likely to become stale. Hide it to avoid confusion. + InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL); + } + } + private static void updateInsetsStateForDisplayCutout(DisplayFrames displayFrames, InsetsState state) { if (displayFrames.mDisplayCutout.getDisplayCutout().isEmpty()) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index bb0278964f85..ac6e75c717ff 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -25,6 +25,7 @@ import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.SyncRtSurfaceTransactionApplier.applyParams; +import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; @@ -91,6 +92,12 @@ class InsetsPolicy { || focusedWin != getNavControlTarget(focusedWin) || focusedWin.getRequestedInsetsState().getSource(ITYPE_NAVIGATION_BAR) .isVisible()); + updateHideNavInputEventReceiver(); + } + + private void updateHideNavInputEventReceiver() { + mPolicy.updateHideNavInputEventReceiver(!isHidden(ITYPE_NAVIGATION_BAR), + mFocusedWin.mAttrs.insetsFlags.behavior != BEHAVIOR_SHOW_BARS_BY_TOUCH); } boolean isHidden(@InternalInsetsType int type) { @@ -169,6 +176,7 @@ class InsetsPolicy { if (windowState == getNavControlTarget(mFocusedWin)) { mNavBar.setVisible(state.getSource(ITYPE_NAVIGATION_BAR).isVisible()); } + updateHideNavInputEventReceiver(); } /** diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 44034edaa4bf..6b39fd2a70e3 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -406,12 +406,11 @@ class KeyguardController { // show on top of the lock screen. In this can we want to dismiss the docked // stack since it will be complicated/risky to try to put the activity on top // of the lock screen in the right fullscreen configuration. - final ActivityStack stack = - mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask(); - if (stack == null) { + final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); + if (!display.isSplitScreenModeActivated()) { return; } - mRootWindowContainer.getDefaultDisplay().onSplitScreenModeDismissed(); + display.onSplitScreenModeDismissed(); } } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index bd5666dd9a27..244ba82ce32c 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.ActivityManager.RECENT_WITH_EXCLUDED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -1298,6 +1299,7 @@ class RecentTasks { switch (task.getActivityType()) { case ACTIVITY_TYPE_HOME: case ACTIVITY_TYPE_RECENTS: + case ACTIVITY_TYPE_DREAM: // Ignore certain activity types completely return false; case ACTIVITY_TYPE_ASSISTANT: diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index adafdec40d37..9089240fe9d1 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -388,11 +388,12 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // surfaces needs to be done immediately. mWindowManager.executeAppTransition(); - if (targetStack.getTile() != null) { + final Task rootTask = targetStack.getRootTask(); + if (rootTask.isOrganized()) { // Client state may have changed during the recents animation, so force // send task info so the client can synchronize its state. mService.mTaskOrganizerController.dispatchTaskInfoChanged( - targetStack.mTile, true /* force */); + rootTask, true /* force */); } } catch (Exception e) { Slog.e(TAG, "Failed to clean up recents activity", e); diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index 45f8a15979f4..420997a5b82d 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -20,7 +20,6 @@ import static com.android.server.wm.ActivityStack.TAG_ADD_REMOVE; import static com.android.server.wm.ActivityStack.TAG_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS; -import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; import android.app.ActivityOptions; import android.content.Intent; @@ -233,48 +232,42 @@ class ResetTargetTaskHelper { } final ActivityTaskManagerService atmService = mTargetStack.mAtmService; - final ArrayList<Task> createdTasks = new ArrayList<>(); + DisplayContent display = mTargetStack.getDisplay(); + final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance(); + if (singleTaskInstanceDisplay) { + display = atmService.mRootWindowContainer.getDefaultDisplay(); + } + + final int windowingMode = mTargetStack.getWindowingMode(); + final int activityType = mTargetStack.getActivityType(); + while (!mPendingReparentActivities.isEmpty()) { final ActivityRecord r = mPendingReparentActivities.remove(0); - final ActivityRecord bottom = mTargetStack.getBottomMostActivity(); - final Task targetTask; - if (bottom != null && r.taskAffinity.equals(bottom.getTask().affinity)) { + final boolean alwaysCreateTask = DisplayContent.alwaysCreateStack(windowingMode, + activityType); + final Task task = alwaysCreateTask + ? display.getBottomMostTask() : mTargetStack.getBottomMostTask(); + Task targetTask = null; + if (task != null && r.taskAffinity.equals(task.affinity)) { // If the activity currently at the bottom has the same task affinity as // the one we are moving, then merge it into the same task. - targetTask = bottom.getTask(); + targetTask = task; if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + r + " out to bottom task " + targetTask); - } else { - targetTask = mTargetStack.reuseOrCreateTask( - r.info, null /*intent*/, false /*toTop*/); + } + if (targetTask == null) { + if (alwaysCreateTask) { + targetTask = display.getOrCreateStack(windowingMode, activityType, + false /* onTop */); + } else { + targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/, + false /*toTop*/); + } targetTask.affinityIntent = r.intent; - createdTasks.add(targetTask); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " - + r + " out to new task " + targetTask); } r.reparent(targetTask, 0 /* position */, "resetTargetTaskIfNeeded"); atmService.mStackSupervisor.mRecentTasks.add(targetTask); } - - DisplayContent display = mTargetStack.getDisplay(); - final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance(); - if (singleTaskInstanceDisplay) { - display = atmService.mRootWindowContainer.getDefaultDisplay(); - } - - final int windowingMode = mTargetStack.getWindowingMode(); - final int activityType = mTargetStack.getActivityType(); - if (!singleTaskInstanceDisplay && !display.alwaysCreateStack(windowingMode, activityType)) { - return; - } - - while (!createdTasks.isEmpty()) { - final Task targetTask = createdTasks.remove(createdTasks.size() - 1); - final ActivityStack targetStack = display.getOrCreateStack( - windowingMode, activityType, false /* onTop */); - targetTask.reparent(targetStack, false /* toTop */, REPARENT_LEAVE_STACK_IN_PLACE, - false /* animate */, true /* deferResume */, "resetTargetTask"); - } } private boolean takeOption(ActivityRecord p, boolean noOptions) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ebf1bc988b91..b2920ee1d4aa 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -1970,8 +1971,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int focusStackId = topFocusedStack != null ? topFocusedStack.getRootTaskId() : INVALID_TASK_ID; // We dismiss the docked stack whenever we switch users. - final ActivityStack dockedStack = getDefaultDisplay().getRootSplitScreenPrimaryTask(); - if (dockedStack != null) { + if (getDefaultDisplay().isSplitScreenModeActivated()) { getDefaultDisplay().onSplitScreenModeDismissed(); } // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will @@ -2110,20 +2110,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final ActivityStack stack; if (singleActivity) { stack = r.getRootTask(); + stack.setWindowingMode(WINDOWING_MODE_PINNED); } else { - // In the case of multiple activities, we will create a new stack for it and then - // move the PIP activity into the stack. - // We will then perform a windowing mode change for both scenarios. - stack = display.createStack( - r.getRootTask().getRequestedOverrideWindowingMode(), - r.getActivityType(), ON_TOP, r.info, r.intent); + // In the case of multiple activities, we will create a new task for it and then + // move the PIP activity into the task. + stack = display.createStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP, + r.info, r.intent, false /* createdByOrganizer */); + // There are multiple activities in the task and moving the top activity should // reveal/leave the other activities in their original task. r.reparent(stack, MAX_VALUE, "moveActivityToStack"); } - stack.setWindowingMode(WINDOWING_MODE_PINNED); - // Reset the state that indicates it can enter PiP while pausing after we've moved it // to the pinned stack r.supportsEnterPipOnTaskSwitch = false; @@ -2799,16 +2797,19 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (stack == null && r != null) { stack = r.getRootTask(); } + int windowingMode = launchParams != null ? launchParams.mWindowingMode + : WindowConfiguration.WINDOWING_MODE_UNDEFINED; if (stack != null) { display = stack.getDisplay(); if (display != null && canLaunchOnDisplay(r, display.mDisplayId)) { - int windowingMode = launchParams != null ? launchParams.mWindowingMode - : WindowConfiguration.WINDOWING_MODE_UNDEFINED; if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { windowingMode = display.resolveWindowingMode(r, options, candidateTask, activityType); } - if (stack.isCompatible(windowingMode, activityType)) { + // Always allow organized tasks that created by organizer since the activity type + // of an organized task is decided by the activity type of its top child, which + // could be incompatible with the given windowing mode and activity type. + if (stack.isCompatible(windowingMode, activityType) || stack.mCreatedByOrganizer) { return stack; } if (windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY @@ -2826,6 +2827,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (display == null || !canLaunchOnDisplay(r, display.mDisplayId)) { display = getDefaultDisplay(); + if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { + windowingMode = display.resolveWindowingMode(r, options, candidateTask, + activityType); + } } return display.getOrCreateStack(r, options, candidateTask, activityType, onTop); @@ -2915,11 +2920,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome(); case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents(); case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant(); + case ACTIVITY_TYPE_DREAM: return r.isActivityTypeDream(); } - // TODO(task-hierarchy): Find another way to differentiate tile from normal stack once it is - // part of the hierarchy - if (stack instanceof TaskTile) { - // Don't launch directly into tiles. + if (stack.mCreatedByOrganizer) { + // Don't launch directly into task created by organizer...but why can't we? return false; } // There is a 1-to-1 relationship between stack and task when not in diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 55dc694bb611..f19c10637c9b 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -20,6 +20,9 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; @@ -114,7 +117,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; @@ -129,11 +131,11 @@ import android.util.DisplayMetrics; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; -import android.window.ITaskOrganizer; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; +import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; @@ -489,6 +491,17 @@ class Task extends WindowContainer<WindowContainer> { PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build(); /** + * This task was created by the task organizer which has the following implementations. + * <ul> + * <lis>The task won't be removed when it is empty. Removal has to be an explicit request + * from the task organizer.</li> + * <li>Unlike other non-root tasks, it's direct children are visible to the task + * organizer for ordering purposes.</li> + * </ul> + */ + boolean mCreatedByOrganizer; + + /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, * ActivityInfo, Intent, TaskDescription)} instead. */ @@ -578,6 +591,14 @@ class Task extends WindowContainer<WindowContainer> { voiceInteractor = _voiceInteractor; setIntent(activity, intent, info); setMinDimensions(info); + // Before we began to reuse a root task (old ActivityStack) as the leaf task, we used to + // create a leaf task in this case. Therefore now we won't send out the task created + // notification when we decide to reuse it here, so we send out the notification below. + // The reason why the created notification sent out when root task is created doesn't work + // is that realActivity isn't set until setIntent() method above is called for the first + // time. Eventually this notification will be removed when we can populate those information + // when root task is created. + mAtmService.getTaskChangeNotificationController().notifyTaskCreated(mTaskId, realActivity); return this; } @@ -586,11 +607,7 @@ class Task extends WindowContainer<WindowContainer> { return; } - // TODO(xutan): Removed type check after stack and task is merged. - // Before the real merge of stack and task, we need to avoid saving state of stacks. Once - // the merge is finished we can just pass DisplayContent because both windowing mode and - // bounds are set in the merged task. - if (oldParent instanceof ActivityStack) { + if (isLeafTask()) { // This task is going away, so save the last state if necessary. saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent()); } @@ -1349,7 +1366,7 @@ class Task extends WindowContainer<WindowContainer> { if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) { return applicationType; } - return getChildAt(0).getActivityType(); + return getTopChild().getActivityType(); } @Override @@ -1362,6 +1379,12 @@ class Task extends WindowContainer<WindowContainer> { ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addChild: %s at top.", this); + // A rootable task that is now being added to be the child of an organized task. Making + // sure the stack references is keep updated. + if (mTaskOrganizer != null && mCreatedByOrganizer && child.asTask() != null) { + mDisplayContent.addStackReferenceIfNeeded((ActivityStack) child); + } + // Make sure the list of display UID whitelists is updated // now that this record is in a new task. mRootWindowContainer.updateUIDsPresentOnDisplay(); @@ -1402,6 +1425,11 @@ class Task extends WindowContainer<WindowContainer> { @Override void removeChild(WindowContainer child) { + // A rootable child task that is now being removed from an organized task. Making sure + // the stack references is keep updated. + if (mTaskOrganizer != null && mCreatedByOrganizer && child.asTask() != null) { + mDisplayContent.removeStackReferenceIfNeeded((ActivityStack) child); + } removeChild(child, "removeChild"); } @@ -1449,8 +1477,9 @@ class Task extends WindowContainer<WindowContainer> { mStackSupervisor.removeTask(this, false /* killProcess */, !REMOVE_FROM_RECENTS, reason); } - } else if (!mReuseTask) { + } else if (!mReuseTask && !mCreatedByOrganizer) { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse + // or created by task organizer. if (!isRootTask) { getStack().removeChild(this, reason); } @@ -1862,7 +1891,12 @@ class Task extends WindowContainer<WindowContainer> { final Task parentTask = getParent().asTask(); if (parentTask != null) { parentTask.onActivityStateChanged(record, state, reason); - return; + // We still want to update the resumed activity if the parent task is created by + // organizer in order to keep the information synced once got reparented out from the + // organized task. + if (!parentTask.mCreatedByOrganizer) { + return; + } } if (record == mResumedActivity && state != RESUMED) { @@ -2296,18 +2330,30 @@ class Task extends WindowContainer<WindowContainer> { return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize); } - void resolveTileOverrideConfiguration(Configuration newParentConfig) { + private void resolveOrganizedOverrideConfiguration(Configuration newParentConfig) { super.resolveOverrideConfiguration(newParentConfig); + if (!isOrganized()) { + return; + } + + final Task root = getRootTask(); + if (root == this) { + return; + } + + // Ensure to have the same windowing mode for the child tasks that controlled by task org. + getResolvedOverrideConfiguration().windowConfiguration + .setWindowingMode(root.getWindowingMode()); } @Override void resolveOverrideConfiguration(Configuration newParentConfig) { - if (!isLeafTask()) { - resolveTileOverrideConfiguration(newParentConfig); + if (!isLeafTask() || mCreatedByOrganizer) { + resolveOrganizedOverrideConfiguration(newParentConfig); return; } mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); - resolveTileOverrideConfiguration(newParentConfig); + resolveOrganizedOverrideConfiguration(newParentConfig); int windowingMode = getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { @@ -2396,7 +2442,9 @@ class Task extends WindowContainer<WindowContainer> { } Rect updateOverrideConfigurationFromLaunchBounds() { - final Rect bounds = getLaunchBounds(); + // If the task is controlled by another organized task, do not set override + // configurations and let its parent (organized task) to control it; + final Rect bounds = isOrganized() && !isRootTask() ? null : getLaunchBounds(); setBounds(bounds); if (bounds != null && !bounds.isEmpty()) { // TODO: Review if we actually want to do this - we are setting the launch bounds @@ -2580,7 +2628,7 @@ class Task extends WindowContainer<WindowContainer> { // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid. if (suggestedPosition == POSITION_BOTTOM && minPosition == 0) { return POSITION_BOTTOM; - } else if (suggestedPosition == POSITION_TOP && maxPosition == (size - 1)) { + } else if (suggestedPosition == POSITION_TOP && maxPosition >= (size - 1)) { return POSITION_TOP; } // Reset position based on minimum/maximum possible positions. @@ -3199,6 +3247,20 @@ class Task extends WindowContainer<WindowContainer> { } @Override + int getOrientation(int candidate) { + return canSpecifyOrientation() ? super.getOrientation(candidate) : SCREEN_ORIENTATION_UNSET; + } + + private boolean canSpecifyOrientation() { + final int windowingMode = getWindowingMode(); + final int activityType = getActivityType(); + return windowingMode == WINDOWING_MODE_FULLSCREEN + || activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_RECENTS + || activityType == ACTIVITY_TYPE_ASSISTANT; + } + + @Override boolean fillsParent() { return matchParentBounds(); } @@ -3364,14 +3426,12 @@ class Task extends WindowContainer<WindowContainer> { info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode(); info.configuration.setTo(getConfiguration()); info.token = mRemoteToken; - // Get's the first non-undefined activity type among this and children. Can't use - // configuration.windowConfiguration because that would only be this level. - info.topActivityType = getActivityType(); //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child // order changes. final Task top = getTopMostTask(); info.resizeMode = top != null ? top.mResizeMode : mResizeMode; + info.topActivityType = top.getActivityType(); if (mPictureInPictureParams.empty()) { info.pictureInPictureParams = null; @@ -3402,10 +3462,6 @@ class Task extends WindowContainer<WindowContainer> { return this; } - TaskTile asTile() { - return null; - } - // TODO(task-merge): Figure-out how this should work with hierarchy tasks. boolean shouldBeVisible(ActivityRecord starting) { return true; @@ -3701,8 +3757,9 @@ class Task extends WindowContainer<WindowContainer> { } static Task create(ActivityTaskManagerService service, int taskId, int activityType, - ActivityInfo info, Intent intent) { - return getTaskFactory().create(service, taskId, activityType, info, intent); + ActivityInfo info, Intent intent, boolean createdByOrganizer) { + return getTaskFactory().create(service, taskId, activityType, info, intent, + createdByOrganizer); } static Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info, @@ -3724,8 +3781,9 @@ class Task extends WindowContainer<WindowContainer> { */ static class TaskFactory { Task create(ActivityTaskManagerService service, int taskId, int activityType, - ActivityInfo info, Intent intent) { - return new ActivityStack(service, taskId, activityType, info, intent); + ActivityInfo info, Intent intent, boolean createdByOrganizer) { + return new ActivityStack(service, taskId, activityType, info, intent, + createdByOrganizer); } Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info, @@ -4001,21 +4059,29 @@ class Task extends WindowContainer<WindowContainer> { @Override boolean isOrganized() { final Task rootTask = getRootTask(); - // if the rootTask is a "child" of a tile, then don't consider it a root task. - // TODO: remove this along with removing tile. - if (((ActivityStack) rootTask).getTile() != null) { + if (rootTask.mTaskOrganizer == null) { + // You are obviously not organized... return false; } - return rootTask == this && rootTask.mTaskOrganizer != null; + if (rootTask == this) { + // Root tasks can be organized. + return true; + } + if (rootTask.mCreatedByOrganizer && getParent() == rootTask) { + // Direct children of tasks added by the organizer can the organized. + return true; + } + + return false; } @Override protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) { /** - * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the - * Surface in to its own hierarchy. + * Avoid reparenting SurfaceControl of the organized tasks that are always on top, since + * the surfaces should be controlled by the organizer itself, like bubbles. */ - if (isOrganized()) { + if (isOrganized() && isAlwaysOnTop()) { return; } super.reparentSurfaceControl(t, newParent); @@ -4050,6 +4116,9 @@ class Task extends WindowContainer<WindowContainer> { mTaskOrganizer = null; mLastTaskOrganizerWindowingMode = -1; onTaskOrganizerChanged(); + if (mCreatedByOrganizer) { + removeImmediately(); + } } /** diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 552367801678..05b721b8ee81 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -27,13 +28,14 @@ import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDO import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; -import android.window.ITaskOrganizerController; import android.window.ITaskOrganizer; +import android.window.ITaskOrganizerController; import android.window.IWindowContainer; import com.android.internal.util.ArrayUtils; @@ -255,11 +257,12 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (display == null) { return null; } - final int nextId = display.getNextStackId(); - TaskTile tile = new TaskTile(mService, nextId, windowingMode); - display.addTile(tile); - RunningTaskInfo out = tile.getTaskInfo(); - mLastSentTaskInfos.put(tile, out); + + final Task task = display.getOrCreateStack(windowingMode, ACTIVITY_TYPE_UNDEFINED, + false /* onTop */, new Intent(), null /* candidateTask */, + true /* createdByOrganizer */); + RunningTaskInfo out = task.getTaskInfo(); + mLastSentTaskInfos.put(task, out); return out; } } finally { @@ -273,11 +276,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - TaskTile tile = TaskTile.forToken(token.asBinder()); - if (tile == null) { - return false; + final Task task = WindowContainer.fromBinder(token.asBinder()).asTask(); + if (task == null) return false; + if (!task.mCreatedByOrganizer) { + throw new IllegalArgumentException( + "Attempt to delete task not created by organizer task=" + task); } - tile.removeImmediately(); + task.removeImmediately(); return true; } } finally { @@ -358,12 +363,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (task == null) { return null; } - ActivityStack rootTask = (ActivityStack) task.getRootTask(); - final TaskTile tile = rootTask.getTile(); - if (tile != null) { - rootTask = tile; - } - return rootTask.mRemoteToken; + return task.getRootTask().mRemoteToken; } } finally { Binder.restoreCallingIdentity(origId); @@ -371,7 +371,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public void setLaunchRoot(int displayId, @Nullable IWindowContainer tile) { + public void setLaunchRoot(int displayId, @Nullable IWindowContainer token) { enforceStackPermission("setLaunchRoot()"); final long origId = Binder.clearCallingIdentity(); try { @@ -380,16 +380,21 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (display == null) { return; } - TaskTile taskTile = tile == null ? null : TaskTile.forToken(tile.asBinder()); - if (taskTile == null) { - display.mLaunchTile = null; + Task task = token == null + ? null : WindowContainer.fromBinder(token.asBinder()).asTask(); + if (task == null) { + display.mLaunchRootTask = null; return; } - if (taskTile.getDisplay() != display) { + if (!task.mCreatedByOrganizer) { + throw new IllegalArgumentException("Attempt to set task not created by " + + "organizer as launch root task=" + task); + } + if (task.getDisplayContent() != display) { throw new RuntimeException("Can't set launch root for display " + displayId - + " to task on display " + taskTile.getDisplay().getDisplayId()); + + " to task on display " + task.getDisplayContent().getDisplayId()); } - display.mLaunchTile = taskTile; + display.mLaunchRootTask = task; } } finally { Binder.restoreCallingIdentity(origId); @@ -411,25 +416,25 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { Slog.e(TAG, "Can't get children of " + parent + " because it is not valid."); return null; } - // For now, only support returning children of persistent root tasks (of which the - // only current implementation is TaskTile). - if (!(container instanceof TaskTile)) { + final Task task = container.asTask(); + if (task == null) { + Slog.e(TAG, container + " is not a task..."); + return null; + } + // For now, only support returning children of tasks created by the organizer. + if (!task.mCreatedByOrganizer) { Slog.w(TAG, "Can only get children of root tasks created via createRootTask"); return null; } ArrayList<RunningTaskInfo> out = new ArrayList<>(); - // Tiles aren't real parents, so we need to go through stacks on the display to - // ensure correct ordering. - final DisplayContent dc = container.getDisplayContent(); - for (int i = dc.getStackCount() - 1; i >= 0; --i) { - final ActivityStack as = dc.getStackAt(i); - if (as.getTile() == container) { - if (activityTypes != null - && !ArrayUtils.contains(activityTypes, as.getActivityType())) { - continue; - } - out.add(as.getTaskInfo()); + for (int i = task.getChildCount() - 1; i >= 0; --i) { + final Task child = task.getChildAt(i).asTask(); + if (child == null) continue; + if (activityTypes != null + && !ArrayUtils.contains(activityTypes, child.getActivityType())) { + continue; } + out.add(child.getTaskInfo()); } return out; } @@ -451,12 +456,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } ArrayList<RunningTaskInfo> out = new ArrayList<>(); for (int i = dc.getStackCount() - 1; i >= 0; --i) { - final ActivityStack task = dc.getStackAt(i); - if (task.getTile() != null) { - // a tile is supposed to look like a parent, so don't include their - // "children" here. They can be accessed via getChildTasks() - continue; - } + final Task task = dc.getStackAt(i); if (activityTypes != null && !ArrayUtils.contains(activityTypes, task.getActivityType())) { continue; diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java deleted file mode 100644 index 51142b1d2eb1..000000000000 --- a/services/core/java/com/android/server/wm/TaskTile.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; -import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - -import android.app.ActivityManager; -import android.app.TaskInfo; -import android.app.WindowConfiguration; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.IBinder; -import android.util.Slog; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.function.Consumer; - -/** - * A Tile. Right now this acts as a proxy for manipulating non-child stacks. Eventually, this - * can become an actual parent. - */ -// TODO(task-hierarchy): Remove when tasks can nest >2 or when single tasks can handle their -// own lifecycles. -public class TaskTile extends ActivityStack { - private static final String TAG = "TaskTile"; - final ArrayList<WindowContainer> mChildren = new ArrayList<>(); - - private static ActivityInfo createEmptyActivityInfo() { - ActivityInfo info = new ActivityInfo(); - info.applicationInfo = new ApplicationInfo(); - return info; - } - - TaskTile(ActivityTaskManagerService atmService, int id, int windowingMode) { - super(atmService, id, new Intent() /*intent*/, null /*affinityIntent*/, null /*affinity*/, - null /*rootAffinity*/, null /*realActivity*/, null /*origActivity*/, - false /*rootWasReset*/, false /*autoRemoveRecents*/, false /*askedCompatMode*/, - 0 /*userId*/, 0 /*effectiveUid*/, null /*lastDescription*/, - System.currentTimeMillis(), true /*neverRelinquishIdentity*/, - new ActivityManager.TaskDescription(), id, INVALID_TASK_ID, INVALID_TASK_ID, - 0 /*taskAffiliationColor*/, 0 /*callingUid*/, "" /*callingPackage*/, - null /*callingFeatureId*/, RESIZE_MODE_RESIZEABLE, - false /*supportsPictureInPicture*/, false /*_realActivitySuspended*/, - false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, - createEmptyActivityInfo(), null /*voiceSession*/, null /*voiceInteractor*/, - null /*stack*/); - getRequestedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); - } - - @Override - void onDisplayChanged(DisplayContent dc) { - mDisplayContent = null; - if (dc != null) { - dc.getPendingTransaction().merge(getPendingTransaction()); - } - mDisplayContent = dc; - // Virtual parent, so don't notify children. - } - - @Override - TaskTile asTile() { - return this; - } - - @Override - protected void addChild(WindowContainer child, Comparator<WindowContainer> comparator) { - throw new RuntimeException("Improper use of addChild() on Tile"); - } - - @Override - void addChild(WindowContainer child, int index) { - mChildren.add(child); - if (child instanceof ActivityStack) { - ((ActivityStack) child).setTile(this); - } - mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( - this, false /* force */); - } - - @Override - void removeChild(WindowContainer child) { - if (child instanceof ActivityStack) { - ((ActivityStack) child).setTile(null); - } - mChildren.remove(child); - mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( - this, false /* force */); - } - - void removeAllChildren() { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer child = mChildren.get(i); - if (child instanceof ActivityStack) { - ((ActivityStack) child).setTile(null); - } - } - mChildren.clear(); - mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( - this, false /* force */); - } - - @Override - protected int getChildCount() { - // Currently 0 as this isn't a proper hierarchy member yet. - return 0; - } - - @Override - public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { - Configuration c = new Configuration(getRequestedOverrideConfiguration()); - c.windowConfiguration.setWindowingMode(windowingMode); - onRequestedOverrideConfigurationChanged(c); - } - - @Override - public void onConfigurationChanged(Configuration newParentConfig) { - super.onConfigurationChanged(newParentConfig); - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer child = mChildren.get(i); - child.onConfigurationChanged(child.getParent().getConfiguration()); - } - } - - void forAllTileActivities(Consumer<ActivityRecord> callback) { - for (int i = mChildren.size() - 1; i >= 0; --i) { - mChildren.get(i).forAllActivities(callback, true /* traverseTopToBottom */); - } - } - - /** - * Until this can be part of the hierarchy, the Stack level can use this utility during - * resolveOverrideConfig to simulate inheritance. - */ - void updateResolvedConfig(Configuration inOutResolvedConfig) { - Rect resolveBounds = inOutResolvedConfig.windowConfiguration.getBounds(); - if (resolveBounds.isEmpty()) { - resolveBounds.set(getRequestedOverrideBounds()); - } - int stackMode = inOutResolvedConfig.windowConfiguration.getWindowingMode(); - if (stackMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED - || stackMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) { - // Also replace FULLSCREEN because we interpret FULLSCREEN as "fill parent" - inOutResolvedConfig.windowConfiguration.setWindowingMode( - getRequestedOverrideWindowingMode()); - } - if (inOutResolvedConfig.smallestScreenWidthDp - == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { - inOutResolvedConfig.smallestScreenWidthDp = - getRequestedOverrideConfiguration().smallestScreenWidthDp; - } - if (inOutResolvedConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - inOutResolvedConfig.screenWidthDp = getRequestedOverrideConfiguration().screenWidthDp; - } - if (inOutResolvedConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - inOutResolvedConfig.screenHeightDp = getRequestedOverrideConfiguration().screenHeightDp; - } - Rect resolveAppBounds = inOutResolvedConfig.windowConfiguration.getAppBounds(); - if (resolveAppBounds == null || resolveAppBounds.isEmpty()) { - inOutResolvedConfig.windowConfiguration.setAppBounds( - getRequestedOverrideConfiguration().windowConfiguration.getAppBounds()); - } - } - - @Override - void fillTaskInfo(TaskInfo info) { - super.fillTaskInfo(info); - WindowContainer top = null; - // Check mChildren.isEmpty directly because hasChild() -> getChildCount() always returns 0 - if (!mChildren.isEmpty()) { - // Find the top-most root task which is a virtual child of this Tile. Because this is a - // virtual parent, the mChildren order here isn't changed during hierarchy operations. - WindowContainer parent = mChildren.get(0).getParent(); - for (int i = parent.getChildCount() - 1; i >= 0; --i) { - if (mChildren.contains(parent.getChildAt(i))) { - top = parent.getChildAt(i); - break; - } - } - } - final Task topTask = top == null ? null : top.getTopMostTask(); - boolean isResizable = topTask == null || topTask.isResizeable(); - info.resizeMode = isResizable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; - info.topActivityType = top == null ? ACTIVITY_TYPE_UNDEFINED : top.getActivityType(); - } - - @Override - void removeImmediately() { - removeAllChildren(); - super.removeImmediately(); - } - - @Override - void taskOrganizerDied() { - super.taskOrganizerDied(); - removeImmediately(); - } - - static TaskTile forToken(IBinder token) { - try { - return (TaskTile) ((RemoteToken) token).getContainer(); - } catch (ClassCastException e) { - Slog.w(TAG, "Bad tile token: " + token, e); - return null; - } - } -} diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index e416e8073a75..5f21e1799958 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -159,17 +159,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub false /* preserveWindow */); try { for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { - final WindowContainer wc = haveConfigChanges.valueAt(i); - final Task task = wc.asTask(); - final TaskTile tile = task != null ? task.asTile() : null; - if (tile != null) { - // Special case for tile. Can't override normal forAllActivities - // because it generates duplicate calls and messes up existing - // code-paths. - tile.forAllTileActivities(f); - } else { - wc.forAllActivities(f); - } + haveConfigChanges.valueAt(i).forAllActivities(f); } } finally { f.recycle(); @@ -223,51 +213,65 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub private int sanitizeAndApplyHierarchyOp(WindowContainer container, WindowContainerTransaction.HierarchyOp hop) { - if (!(container instanceof Task)) { + final Task task = container.asTask(); + if (task == null) { throw new IllegalArgumentException("Invalid container in hierarchy op"); } - if (container.getDisplayContent() == null) { - Slog.w(TAG, "Container is no longer attached: " + container); + final DisplayContent dc = task.getDisplayContent(); + if (dc == null) { + Slog.w(TAG, "Container is no longer attached: " + task); return 0; } + final ActivityStack as = (ActivityStack) task; + if (hop.isReparent()) { - // special case for tiles since they are "virtual" parents - if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) { - ActivityStack as = (ActivityStack) container; - TaskTile newParent = hop.getNewParent() == null ? null - : (TaskTile) WindowContainer.fromBinder(hop.getNewParent()); - if (as.getTile() != newParent) { - if (as.getTile() != null) { - as.getTile().removeChild(as); - } - if (newParent != null) { - if (!as.affectedBySplitScreenResize()) { - return 0; - } - newParent.addChild(as, POSITION_TOP); + final boolean isNonOrganizedRootableTask = + (task.isRootTask() && !task.mCreatedByOrganizer) + || task.getParent().asTask().mCreatedByOrganizer; + if (isNonOrganizedRootableTask) { + Task newParent = hop.getNewParent() == null ? null + : WindowContainer.fromBinder(hop.getNewParent()).asTask(); + if (task.getParent() != newParent) { + if (newParent == null) { + // Re-parent task to display as a root task. + dc.moveStackToDisplay(as, hop.getToTop()); + } else if (newParent.inMultiWindowMode() && !task.isResizeable() + && task.isLeafTask()) { + Slog.w(TAG, "Can't support task that doesn't support multi-window mode in" + + " multi-window mode... newParent=" + newParent + " task=" + task); + return 0; + } else { + // Clear the window crop on root task since it may not be updated after + // reparent (no longer be a root task) + task.getSurfaceControl().setWindowCrop(null); + task.reparent((ActivityStack) newParent, + hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, + false /*moveParents*/, "sanitizeAndApplyHierarchyOp"); } - } - if (hop.getToTop()) { - as.getDisplay().positionStackAtTop(as, false /* includingParents */); } else { - as.getDisplay().positionStackAtBottom(as); + final ActivityStack rootTask = + (ActivityStack) (newParent != null ? newParent : task.getRootTask()); + if (hop.getToTop()) { + as.getDisplay().positionStackAtTop(rootTask, false /* includingParents */); + } else { + as.getDisplay().positionStackAtBottom(rootTask); + } } - } else if (container instanceof Task) { + } else { throw new RuntimeException("Reparenting leaf Tasks is not supported now."); } } else { // Ugh, of course ActivityStack has its own special reorder logic... - if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) { - ActivityStack as = (ActivityStack) container; + if (task.isRootTask()) { if (hop.getToTop()) { - as.getDisplay().positionStackAtTop(as, false /* includingParents */); + dc.positionStackAtTop(as, false /* includingParents */); } else { - as.getDisplay().positionStackAtBottom(as); + dc.positionStackAtBottom(as); } } else { - container.getParent().positionChildAt( + task.getParent().positionChildAt( hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, - container, false /* includingParents */); + task, false /* includingParents */); } } return TRANSACT_EFFECTS_LIFECYCLE; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 161152ba0d74..0338cc3096ea 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -131,6 +131,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT; import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET; @@ -138,7 +139,6 @@ import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT; -import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT; import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING; import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; @@ -3338,7 +3338,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final ActivityStack stack = task.getStack(); - if (stack == null) { + if (stack == null || stack.mCreatedByOrganizer) { return; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 023a1e8ede9f..09fab3e29fe0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4434,6 +4434,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { clearDeviceOwnerLocked(getDeviceOwnerAdminLocked(), userHandle); } if (isProfileOwner(adminReceiver, userHandle)) { + if (isProfileOwnerOfOrganizationOwnedDevice(userHandle)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + false, + UserHandle.of(getProfileParentId(userHandle))); + } final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle, /* parent */ false); clearProfileOwnerLocked(admin, userHandle); @@ -13070,26 +13075,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean addingProfileRestricted = mUserManager.hasUserRestriction( UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle); - UserInfo parentUser = mUserManager.getProfileParent(callingUserId); - final boolean addingProfileRestrictedOnParent = (parentUser != null) - && mUserManager.hasUserRestriction( - UserManager.DISALLOW_ADD_MANAGED_PROFILE, - UserHandle.of(parentUser.id)); - - Slog.i(LOG_TAG, String.format( - "When checking for managed profile provisioning: Has device owner? %b, adding" - + " profile restricted? %b, adding profile restricted on parent? %b", - hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent)); + if (mUserManager.getUserInfo(callingUserId).isProfile()) { + Slog.i(LOG_TAG, + String.format("Calling user %d is a profile, cannot add another.", + callingUserId)); + // The check is called from inside a managed profile. A managed profile cannot + // be provisioned from within another managed profile. + return CODE_CANNOT_ADD_MANAGED_PROFILE; + } - // If there's a device owner, the restriction on adding a managed profile must be set - // somewhere. - if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) { + // If there's a device owner, the restriction on adding a managed profile must be set. + if (hasDeviceOwner && !addingProfileRestricted) { Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile."); } - // Do not allow adding a managed profile if there's a restriction, either on the current - // user or its parent user. - if (addingProfileRestricted || addingProfileRestrictedOnParent) { + // Do not allow adding a managed profile if there's a restriction. + if (addingProfileRestricted) { + Slog.i(LOG_TAG, String.format( + "Adding a profile is restricted: User %s Has device owner? %b", + callingUserHandle, hasDeviceOwner)); return CODE_CANNOT_ADD_MANAGED_PROFILE; } // If there's a restriction on removing the managed profile then we have to take it @@ -13098,6 +13102,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle); if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { + Slog.i(LOG_TAG, String.format( + "Cannot add more profiles: Can remove current? %b", canRemoveProfile)); return CODE_CANNOT_ADD_MANAGED_PROFILE; } } finally { diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 1212f2082404..109c1191523b 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -15,218 +15,227 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.servicestests"> - - <uses-permission android:name="android.permission.READ_LOGS" /> - <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" /> - <uses-permission android:name="android.permission.ACCOUNT_MANAGER" /> - <uses-permission android:name="android.permission.WRITE_SETTINGS" /> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> - <uses-permission android:name="android.permission.READ_PHONE_STATE" /> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.BROADCAST_STICKY" /> - <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> - <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> - <uses-permission android:name="android.permission.REAL_GET_TASKS" /> - <uses-permission android:name="android.permission.GET_DETAILED_TASKS" /> - <uses-permission android:name="android.permission.REORDER_TASKS" /> - <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> - <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> - <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> - <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> - <uses-permission android:name="android.permission.MANAGE_USERS" /> - <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> - <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> - <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> - <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> - <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> - <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> - <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> - <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> - <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> - <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> - <uses-permission android:name="android.permission.DELETE_PACKAGES" /> - <uses-permission android:name="android.permission.GET_APP_OPS_STATS" /> - <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> + package="com.android.frameworks.servicestests"> + + <uses-permission android:name="android.permission.READ_LOGS"/> + <uses-permission android:name="android.permission.ACCESS_VR_MANAGER"/> + <uses-permission android:name="android.permission.ACCOUNT_MANAGER"/> + <uses-permission android:name="android.permission.WRITE_SETTINGS"/> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/> + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.BROADCAST_STICKY"/> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/> + <uses-permission android:name="android.permission.MANAGE_APP_TOKENS"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/> + <uses-permission android:name="android.permission.REAL_GET_TASKS"/> + <uses-permission android:name="android.permission.GET_DETAILED_TASKS"/> + <uses-permission android:name="android.permission.REORDER_TASKS"/> + <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/> + <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY"/> + <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> + <uses-permission android:name="android.permission.MANAGE_USERS"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> + <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS"/> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/> + <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/> + <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"/> + <uses-permission android:name="android.permission.INSTALL_PACKAGES"/> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> + <uses-permission android:name="android.permission.DELETE_PACKAGES"/> + <uses-permission android:name="android.permission.GET_APP_OPS_STATS"/> + <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/> <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES"/> - <uses-permission android:name="android.permission.DEVICE_POWER" /> - <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" /> - <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" /> - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> - <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> - <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> - <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.STORAGE_INTERNAL" /> - <uses-permission android:name="android.permission.WATCH_APPOPS" /> + <uses-permission android:name="android.permission.DEVICE_POWER"/> + <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/> + <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> + <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/> + <uses-permission android:name="android.permission.READ_FRAME_BUFFER"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.STORAGE_INTERNAL"/> + <uses-permission android:name="android.permission.WATCH_APPOPS"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.SUSPEND_APPS"/> - <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> - <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> <uses-permission android:name="android.permission.CONTROL_KEYGUARD"/> <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/> - <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> - <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> - <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/> + <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.BLUETOOTH"/> - <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> - <uses-permission android:name="android.permission.DUMP" /> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> + <uses-permission android:name="android.permission.DUMP"/> <uses-permission android:name="android.permission.READ_DREAM_STATE"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" - android:targetSdkVersion="26"/> + android:targetSdkVersion="26"/> <application android:testOnly="true"> - <uses-library android:name="android.test.runner" /> + <uses-library android:name="android.test.runner"/> <service android:name="com.android.server.accounts.TestAccountType1AuthenticatorService" - android:exported="false"> + android:exported="false"> <intent-filter> - <action android:name="android.accounts.AccountAuthenticator" /> + <action android:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" - android:resource="@xml/test_account_type1_authenticator" /> + android:resource="@xml/test_account_type1_authenticator"/> </service> <service android:name="com.android.server.accounts.TestAccountType2AuthenticatorService" - android:exported="false"> + android:exported="false"> <intent-filter> - <action android:name="android.accounts.AccountAuthenticator" /> + <action android:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" - android:resource="@xml/test_account_type2_authenticator" /> + android:resource="@xml/test_account_type2_authenticator"/> </service> <receiver android:name="com.android.server.devicepolicy.ApplicationRestrictionsTest$AdminReceiver" - android:permission="android.permission.BIND_DEVICE_ADMIN"> + android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" - android:resource="@xml/device_admin_sample" /> + android:resource="@xml/device_admin_sample"/> <intent-filter> - <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="com.android.server.devicepolicy.DummyDeviceAdmins$Admin1" - android:permission="android.permission.BIND_DEVICE_ADMIN"> + android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" - android:resource="@xml/device_admin_sample" /> + android:resource="@xml/device_admin_sample"/> <intent-filter> - <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="com.android.server.devicepolicy.DummyDeviceAdmins$Admin2" - android:permission="android.permission.BIND_DEVICE_ADMIN"> + android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" - android:resource="@xml/device_admin_sample" /> + android:resource="@xml/device_admin_sample"/> <intent-filter> - <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="com.android.server.devicepolicy.DummyDeviceAdmins$Admin3" - android:permission="android.permission.BIND_DEVICE_ADMIN"> + android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" - android:resource="@xml/device_admin_sample" /> + android:resource="@xml/device_admin_sample"/> <intent-filter> - <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="com.android.server.devicepolicy.DummyDeviceAdmins$AdminNoPerm"> <meta-data android:name="android.app.device_admin" - android:resource="@xml/device_admin_sample" /> + android:resource="@xml/device_admin_sample"/> <intent-filter> - <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <service android:name="com.android.server.job.MockPriorityJobService" - android:permission="android.permission.BIND_JOB_SERVICE" /> + android:permission="android.permission.BIND_JOB_SERVICE"/> - <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity" /> - <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity2" /> - <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity3" /> + <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity"/> + <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity2"/> + <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity3"/> <activity android:name="com.android.server.pm.ShortcutTestActivity" - android:enabled="true" android:exported="true" /> + android:enabled="true" + android:exported="true"/> <activity android:name="com.android.server.pm.SuspendedDetailsActivity" - android:enabled="true" - android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"> + android:enabled="true" + android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"> <intent-filter> - <action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS" /> - <category android:name="android.intent.category.DEFAULT" /> + <action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS"/> + <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> - <activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity" /> - <activity android:name="com.android.server.adb.AdbDebuggingManagerTestActivity" /> + <activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity"/> + <activity android:name="com.android.server.adb.AdbDebuggingManagerTestActivity"/> <activity-alias android:name="a.ShortcutEnabled" - android:targetActivity="com.android.server.pm.ShortcutTestActivity" - android:enabled="true" android:exported="true"> + android:targetActivity="com.android.server.pm.ShortcutTestActivity" + android:enabled="true" + android:exported="true"> </activity-alias> <activity-alias android:name="a.ShortcutDisabled" - android:targetActivity="com.android.server.pm.ShortcutTestActivity" - android:enabled="false" android:exported="true"> - <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_5"/> + android:targetActivity="com.android.server.pm.ShortcutTestActivity" + android:enabled="false" + android:exported="true"> + <meta-data android:name="android.app.shortcuts" + android:resource="@xml/shortcut_5"/> </activity-alias> <activity-alias android:name="a.ShortcutUnexported" - android:targetActivity="com.android.server.pm.ShortcutTestActivity" - android:enabled="true" android:exported="false"> - <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_5"/> + android:targetActivity="com.android.server.pm.ShortcutTestActivity" + android:enabled="true" + android:exported="false"> + <meta-data android:name="android.app.shortcuts" + android:resource="@xml/shortcut_5"/> </activity-alias> <activity-alias android:name="a.Shortcut1" - android:targetActivity="com.android.server.pm.ShortcutTestActivity" - android:enabled="true" android:exported="true"> - <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_1"/> + android:targetActivity="com.android.server.pm.ShortcutTestActivity" + android:enabled="true" + android:exported="true"> + <meta-data android:name="android.app.shortcuts" + android:resource="@xml/shortcut_1"/> </activity-alias> <activity-alias android:name="a.ShortcutConfigActivity" - android:targetActivity="com.android.server.pm.ShortcutTestActivity"> + android:targetActivity="com.android.server.pm.ShortcutTestActivity"> <intent-filter> - <action android:name="android.intent.action.CREATE_SHORTCUT" /> + <action android:name="android.intent.action.CREATE_SHORTCUT"/> </intent-filter> </activity-alias> <activity-alias android:name="a.DisabledMain" - android:targetActivity="com.android.server.pm.ShortcutTestActivity" - android:enabled="false" android:exported="true"> + android:targetActivity="com.android.server.pm.ShortcutTestActivity" + android:enabled="false" + android:exported="true"> <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity-alias> <activity-alias android:name="a.UnexportedMain" - android:targetActivity="com.android.server.pm.ShortcutTestActivity" - android:enabled="true" android:exported="false"> + android:targetActivity="com.android.server.pm.ShortcutTestActivity" + android:enabled="true" + android:exported="false"> <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity-alias> <receiver android:name="com.android.server.appwidget.DummyAppWidget"> <intent-filter> - <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> + <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> </intent-filter> <meta-data android:name="android.appwidget.provider" - android:resource="@xml/dummy_appwidget_info" /> + android:resource="@xml/dummy_appwidget_info"/> </receiver> </application> - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.frameworks.servicestests" - android:label="Frameworks Services Tests" /> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.servicestests" + android:label="Frameworks Services Tests"/> </manifest> diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 7c6ac1717651..baf551e756e8 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3205,6 +3205,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) .thenReturn(true); setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); + when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; } @@ -3246,6 +3247,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) .thenReturn(true); setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); + when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; } @@ -3617,14 +3619,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); - when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false); when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE)) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, true)).thenReturn(true); setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE); - mContext.binder.callingUid = DpmMockContext.CALLER_UID; + mContext.binder.callingUid = DpmMockContext.ANOTHER_UID; } public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_primaryUser() diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java index d7fc97c8722d..2578ca892520 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -33,6 +34,7 @@ import android.app.NotificationChannel; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ShortcutInfo; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; @@ -110,8 +112,10 @@ public class BubbleCheckerTest extends UiServiceTestCase { void setUpShortcutBubble(boolean isValid) { when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID); - when(mShortcutHelper.hasValidShortcutInfo(SHORTCUT_ID, PKG, mUserHandle)) - .thenReturn(isValid); + ShortcutInfo info = mock(ShortcutInfo.class); + when(info.getId()).thenReturn(SHORTCUT_ID); + when(mShortcutHelper.getValidShortcutInfo(SHORTCUT_ID, PKG, mUserHandle)) + .thenReturn(isValid ? info : null); when(mBubbleMetadata.getIntent()).thenReturn(null); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 3c2944560435..d2417f9d10c5 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6372,13 +6372,41 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ShortcutInfo si = mock(ShortcutInfo.class); when(si.getShortLabel()).thenReturn("Hello"); + when(si.isLongLived()).thenReturn(true); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si)); List<ConversationChannelWrapper> conversations = mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); assertEquals(si, conversations.get(0).getShortcutInfo()); assertEquals(si, conversations.get(1).getShortcutInfo()); + } + @Test + public void testGetConversationsForPackage_shortcut_notLongLived() throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + ArrayList<ConversationChannelWrapper> convos = new ArrayList<>(); + ConversationChannelWrapper convo1 = new ConversationChannelWrapper(); + NotificationChannel channel1 = new NotificationChannel("a", "a", 1); + channel1.setConversationId("parent1", "convo 1"); + convo1.setNotificationChannel(channel1); + convos.add(convo1); + + ConversationChannelWrapper convo2 = new ConversationChannelWrapper(); + NotificationChannel channel2 = new NotificationChannel("b", "b", 1); + channel2.setConversationId("parent1", "convo 2"); + convo2.setNotificationChannel(channel2); + convos.add(convo2); + when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); + + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getShortLabel()).thenReturn("Hello"); + when(si.isLongLived()).thenReturn(false); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si)); + + List<ConversationChannelWrapper> conversations = + mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); + assertNull(conversations.get(0).getShortcutInfo()); + assertNull(conversations.get(1).getShortcutInfo()); } @Test 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 6ec0f919ced7..ed400ea8e992 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -82,9 +82,8 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.view.Gravity; -import android.window.IWindowContainer; -import android.view.SurfaceControl; import android.window.ITaskOrganizer; +import android.window.IWindowContainer; import androidx.test.filters.SmallTest; @@ -975,7 +974,7 @@ public class ActivityStarterTests extends ActivityTestsBase { // Move activity to split-screen-primary stack and make sure it has the focus. TestSplitOrganizer splitOrg = new TestSplitOrganizer(mService, top.getDisplayId()); - splitOrg.mPrimary.addChild(top.getRootTask(), 0 /* index */); + top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM); top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent"); // Activity must landed on split-screen-secondary when launch adjacent. @@ -1001,8 +1000,8 @@ public class ActivityStarterTests extends ActivityTestsBase { static class TestSplitOrganizer extends ITaskOrganizer.Stub { final ActivityTaskManagerService mService; - TaskTile mPrimary; - TaskTile mSecondary; + Task mPrimary; + Task mSecondary; boolean mInSplit = false; int mDisplayId; TestSplitOrganizer(ActivityTaskManagerService service, int displayId) { @@ -1014,10 +1013,10 @@ public class ActivityStarterTests extends ActivityTestsBase { WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); IWindowContainer primary = mService.mTaskOrganizerController.createRootTask( displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token; - mPrimary = TaskTile.forToken(primary.asBinder()); + mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask(); IWindowContainer secondary = mService.mTaskOrganizerController.createRootTask( displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token; - mSecondary = TaskTile.forToken(secondary.asBinder()); + mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask(); } @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo info) { @@ -1042,7 +1041,7 @@ public class ActivityStarterTests extends ActivityTestsBase { for (int i = dc.getStackCount() - 1; i >= 0; --i) { if (!WindowConfiguration.isSplitScreenWindowingMode( dc.getStackAt(i).getWindowingMode())) { - mSecondary.addChild(dc.getStackAt(i), 0); + dc.getStackAt(i).reparent(mSecondary, POSITION_BOTTOM); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 4634e2d71573..716369d49036 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -464,7 +464,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { ActivityStack build() { final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId(); final ActivityStack stack = mDisplay.createStackUnchecked(mWindowingMode, - mActivityType, stackId, mOnTop, mInfo, mIntent); + mActivityType, stackId, mOnTop, mInfo, mIntent, false /* createdByOrganizer */); final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor; if (mCreateActivity) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index a96f40143e0f..ed635ce3f69e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -37,6 +37,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -347,45 +348,62 @@ public class TaskOrganizerTests extends WindowTestsBase { assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType); DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); - List<TaskTile> infos = getTaskTiles(dc); + List<Task> infos = getTasksCreatedByOrganizer(dc); assertEquals(2, infos.size()); assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token)); - infos = getTaskTiles(dc); + infos = getTasksCreatedByOrganizer(dc); assertEquals(1, infos.size()); assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode()); } @Test public void testTileAddRemoveChild() { + ITaskOrganizer listener = new ITaskOrganizer.Stub() { + @Override + public void onTaskAppeared(RunningTaskInfo taskInfo) { } + + @Override + public void onTaskVanished(RunningTaskInfo container) { } + + @Override + public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException { + } + }; + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); final ActivityStack stack = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); - TaskTile tile1 = TaskTile.forToken(info1.token.asBinder()); - tile1.addChild(stack, 0 /* index */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertEquals(info1.configuration.windowConfiguration.getWindowingMode(), stack.getWindowingMode()); // Info should reflect new membership - List<TaskTile> tiles = getTaskTiles(mDisplayContent); - info1 = tiles.get(0).getTaskInfo(); + List<Task> infos = getTasksCreatedByOrganizer(mDisplayContent); + info1 = infos.get(0).getTaskInfo(); assertEquals(ACTIVITY_TYPE_STANDARD, info1.topActivityType); // Children inherit configuration Rect newSize = new Rect(10, 10, 300, 300); - Configuration c = new Configuration(tile1.getRequestedOverrideConfiguration()); + Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask(); + Configuration c = new Configuration(task1.getRequestedOverrideConfiguration()); c.windowConfiguration.setBounds(newSize); doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any()); - tile1.onRequestedOverrideConfigurationChanged(c); + task1.onRequestedOverrideConfigurationChanged(c); assertEquals(newSize, stack.getBounds()); - tile1.removeChild(stack); + wct = new WindowContainerTransaction(); + wct.reparent(stack.mRemoteToken, null, true /* onTop */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); - tiles = getTaskTiles(mDisplayContent); - info1 = tiles.get(0).getTaskInfo(); + infos = getTasksCreatedByOrganizer(mDisplayContent); + info1 = infos.get(0).getTaskInfo(); assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); } @@ -415,8 +433,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ActivityStack stack = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); - TaskTile tile1 = TaskTile.forToken(info1.token.asBinder()); - tile1.addChild(stack, 0 /* index */); + Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask(); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); @@ -424,19 +444,24 @@ public class TaskOrganizerTests extends WindowTestsBase { called[0] = false; final ActivityStack stack2 = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); - tile1.addChild(stack2, 0 /* index */); + wct = new WindowContainerTransaction(); + wct.reparent(stack2.mRemoteToken, info1.token, true /* onTop */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType); lastReportedTiles.clear(); called[0] = false; - mDisplayContent.positionStackAtTop(stack, false /* includingParents */); + task1.positionChildAt(POSITION_TOP, stack, false /* includingParents */); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); lastReportedTiles.clear(); called[0] = false; - tile1.removeAllChildren(); + wct = new WindowContainerTransaction(); + wct.reparent(stack.mRemoteToken, null, true /* onTop */); + wct.reparent(stack2.mRemoteToken, null, true /* onTop */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType); } @@ -457,9 +482,11 @@ public class TaskOrganizerTests extends WindowTestsBase { } }; mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( + listener, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); @@ -522,13 +549,11 @@ public class TaskOrganizerTests extends WindowTestsBase { lastReportedTiles.get(info1.token.asBinder()).topActivityType); } - private List<TaskTile> getTaskTiles(DisplayContent dc) { - ArrayList<TaskTile> out = new ArrayList<>(); + private List<Task> getTasksCreatedByOrganizer(DisplayContent dc) { + ArrayList<Task> out = new ArrayList<>(); for (int i = dc.getStackCount() - 1; i >= 0; --i) { - final TaskTile t = dc.getStackAt(i).asTile(); - if (t != null) { - out.add(t); - } + final Task t = dc.getStackAt(i); + if (t.mCreatedByOrganizer) out.add(t); } return out; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index a25acae3c036..56c19a47b68a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -27,6 +27,7 @@ import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED; @@ -151,7 +152,7 @@ public class TaskRecordTests extends ActivityTestsBase { assertFalse(factory.mCreated); Task.create(mService, 0 /*taskId*/, 0 /*activityType*/, - new ActivityInfo(), new Intent()); + new ActivityInfo(), new Intent(), false /* createdByOrganizer */); assertTrue(factory.mCreated); } finally { @@ -939,6 +940,20 @@ public class TaskRecordTests extends ActivityTestsBase { verify(persister, never()).saveTask(same(task), any()); } + @Test + public void testNotSpecifyOrientationByFloatingTask() { + final Task task = getTestTask(); + final ActivityRecord activity = task.getTopMostActivity(); + final WindowContainer<?> taskContainer = task.getParent(); + activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskContainer.getOrientation()); + + task.setWindowingMode(WINDOWING_MODE_PINNED); + + assertEquals(SCREEN_ORIENTATION_UNSET, taskContainer.getOrientation()); + } + private Task getTestTask() { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); return stack.getBottomMostTask(); @@ -1000,7 +1015,7 @@ public class TaskRecordTests extends ActivityTestsBase { @Override Task create(ActivityTaskManagerService service, int taskId, int activityType, - ActivityInfo info, Intent intent) { + ActivityInfo info, Intent intent, boolean createdByOrganizer) { mCreated = true; return null; } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 3196758996be..00cb6dc7400d 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -207,7 +207,13 @@ public class SoundTriggerService extends SystemService { @Override public void onBootPhase(int phase) { - if (PHASE_SYSTEM_SERVICES_READY == phase) { + Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode()); + if (PHASE_DEVICE_SPECIFIC_SERVICES_READY == phase) { + if (isSafeMode()) { + Slog.w(TAG, "not enabling SoundTriggerService in safe mode"); + return; + } + initSoundTriggerHelper(); mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper); } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) { diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java index 488ee78f6230..47bf14892ccb 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java +++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java @@ -23,6 +23,9 @@ import android.util.Log; import com.android.server.wm.ActivityMetricsLaunchObserver; +import java.io.StringWriter; +import java.io.PrintWriter; + /** * A validator to check the correctness of event sequence during app startup. * @@ -100,7 +103,8 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onIntentStarted(@NonNull Intent intent, long timestampNs) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "IntentStarted during UNKNOWN." + intent); + logWarningWithStackTrace( + String.format("IntentStarted during UNKNOWN. " + intent)); incAccIntentStartedEvents(); return; } @@ -110,7 +114,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { state != State.ACTIVITY_CANCELLED && state != State.ACTIVITY_FINISHED && state != State.REPORT_FULLY_DRAWN) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED)); incAccIntentStartedEvents(); incAccIntentStartedEvents(); @@ -124,12 +128,12 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onIntentFailed() { if (state == State.UNKNOWN) { - Log.wtf(TAG, "IntentFailed during UNKNOWN."); + logWarningWithStackTrace(String.format("onIntentFailed during UNKNOWN.")); decAccIntentStartedEvents(); return; } if (state != State.INTENT_STARTED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED)); incAccIntentStartedEvents(); return; @@ -143,11 +147,12 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, @Temperature int temperature) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onActivityLaunched during UNKNOWN."); + logWarningWithStackTrace( + String.format("onActivityLaunched during UNKNOWN.")); return; } if (state != State.INTENT_STARTED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); incAccIntentStartedEvents(); return; @@ -160,12 +165,13 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onActivityLaunchCancelled during UNKNOWN."); + logWarningWithStackTrace( + String.format("onActivityLaunchCancelled during UNKNOWN.")); decAccIntentStartedEvents(); return; } if (state != State.ACTIVITY_LAUNCHED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED)); incAccIntentStartedEvents(); return; @@ -179,13 +185,14 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, long timestampNs) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onActivityLaunchFinished during UNKNOWN."); + logWarningWithStackTrace( + String.format("onActivityLaunchFinished during UNKNOWN.")); decAccIntentStartedEvents(); return; } if (state != State.ACTIVITY_LAUNCHED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED)); incAccIntentStartedEvents(); return; @@ -199,7 +206,8 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, long timestampNs) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onReportFullyDrawn during UNKNOWN."); + logWarningWithStackTrace( + String.format("onReportFullyDrawn during UNKNOWN.")); return; } if (state == State.INIT) { @@ -207,7 +215,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { } if (state != State.ACTIVITY_FINISHED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); return; } @@ -252,4 +260,11 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { Log.i(TAG, String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents)); } + + private void logWarningWithStackTrace(String log) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + new Throwable("EventSequenceValidator#getStackTrace").printStackTrace(pw); + Log.w(TAG, String.format("%s\n%s", log, sw)); + } } diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 2f9a1c8ade0d..9be97b505a3f 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -509,16 +509,68 @@ public class AppLaunch extends InstrumentationTestCase { for (int i = 0; i < IORAP_COMPILE_CMD_TIMEOUT; ++i) { IorapCompilationStatus status = waitForIorapCompiled(appPkgName); if (status == IorapCompilationStatus.COMPLETE) { + Log.v(TAG, "compileAppForIorap: success"); + logDumpsysIorapd(appPkgName); return true; } else if (status == IorapCompilationStatus.INSUFFICIENT_TRACES) { + Log.e(TAG, "compileAppForIorap: failed due to insufficient traces"); + logDumpsysIorapd(appPkgName); return false; } // else INCOMPLETE. keep asking iorapd if it's done yet. sleep(1000); } + Log.e(TAG, "compileAppForIorap: failed due to timeout"); + logDumpsysIorapd(appPkgName); return false; } + /** Save the contents of $(adb shell dumpsys iorapd) to the launch_logs directory. */ + private void logDumpsysIorapd(String packageName) throws IOException { + InstrumentationTestRunner instrumentation = + (InstrumentationTestRunner)getInstrumentation(); + Bundle args = instrumentation.getArguments(); + + String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); + + // Root directory for applaunch file to log the app launch output + // Will be useful in case of simpleperf command is used + File launchRootDir = null; + if (null != launchDirectory && !launchDirectory.isEmpty()) { + launchRootDir = new File(launchDirectory); + if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { + throw new IOException("Unable to create the destination directory " + + launchRootDir + ". Try disabling selinux."); + } + } else { + Log.w(TAG, "logDumpsysIorapd: Missing launch-directory arg"); + return; + } + + File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); + + if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { + throw new IOException("Unable to create the lauch file sub directory " + + launchSubDir + ". Try disabling selinux."); + } + String path = "iorapd_dumpsys_" + packageName + "_" + System.nanoTime() + ".txt"; + File file = new File(launchSubDir, path); + try (FileOutputStream outputStream = new FileOutputStream(file); + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(outputStream)); + ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). + executeShellCommand(IORAP_DUMPSYS_CMD); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + new FileInputStream(result.getFileDescriptor())))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + writer.write(line + "\n"); + } + } + + Log.v(TAG, "logDumpsysIorapd: Saved to file: " + path); + } + enum IorapCompilationStatus { INCOMPLETE, COMPLETE, diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml index 269cec1ccca9..7b85cc84f1f5 100644 --- a/tests/RollbackTest/RollbackTest.xml +++ b/tests/RollbackTest/RollbackTest.xml @@ -23,6 +23,10 @@ <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package "com.google.android.gms.platformconfigurator" --es user '\\*' --esa flags "ModuleConfig__versioned_immediate_commit_packages" --esa types "bytes" --esa values "Cm5vdGFwYWNrYWdlOgA=" com.google.android.gms" /> <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__immediate_commit_packages" com.google.android.gms" /> <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__versioned_immediate_commit_packages" com.google.android.gms" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.tests.rollback" /> diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 5a92d6849434..cab8b4258bc8 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -75,6 +75,12 @@ public class RollbackTest { private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = "enable_rollback_timeout"; + private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) { + return rollbacks.stream().anyMatch( + ri -> ri.getPackages().stream().anyMatch( + pri -> packageName.equals(pri.getPackageName()))); + } + /** * Test basic rollbacks. */ @@ -113,18 +119,14 @@ public class RollbackTest { // Uninstall TestApp.A Uninstall.packages(TestApp.A); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - // TODO: There is currently a race condition between when the app is - // uninstalled and when rollback manager deletes the rollback. Fix it - // so that's not the case! for (int i = 0; i < 5; ++i) { - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - if (rollback != null) { + if (hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)) { Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect."); Thread.sleep(1000); } } + assertThat(hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)).isFalse(); // The app should not be available for rollback. waitForUnavailableRollback(TestApp.A); diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java index 548af0c54b03..498cb7c1c710 100644 --- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java @@ -18,7 +18,6 @@ package com.google.android.test.windowinsetstests; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; - import static java.lang.Math.max; import static java.lang.Math.min; @@ -31,6 +30,7 @@ import android.content.Context; import android.graphics.Insets; import android.os.Bundle; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -44,11 +44,11 @@ import android.view.WindowInsetsController.OnControllableInsetsChangedListener; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; -import androidx.appcompat.app.AppCompatActivity; - import java.util.ArrayList; import java.util.List; +import androidx.appcompat.app.AppCompatActivity; + public class WindowInsetsActivity extends AppCompatActivity { private View mRoot; @@ -191,6 +191,40 @@ public class WindowInsetsActivity extends AppCompatActivity { mTransitions.forEach(it -> it.onFinish(animation)); } }); + + findViewById(R.id.floating_action_button).setOnClickListener( + v -> v.getWindowInsetsController().controlWindowInsetsAnimation(ime(), -1, + new LinearInterpolator(), null /* cancellationSignal */, + new WindowInsetsAnimationControlListener() { + @Override + public void onReady( + WindowInsetsAnimationController controller, + int types) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(1500); + anim.addUpdateListener(animation + -> controller.setInsetsAndAlpha( + controller.getShownStateInsets(), + (float) animation.getAnimatedValue(), + anim.getAnimatedFraction())); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + controller.finish(true); + } + }); + anim.start(); + } + + @Override + public void onCancelled(WindowInsetsAnimationController controller) { + } + + @Override + public void onFinished(WindowInsetsAnimationController controller) { + } + })); } @Override @@ -200,57 +234,6 @@ public class WindowInsetsActivity extends AppCompatActivity { getWindow().getDecorView().post(() -> getWindow().setDecorFitsSystemWindows(false)); } - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - getWindow().getInsetsController().addOnControllableInsetsChangedListener( - new OnControllableInsetsChangedListener() { - - boolean hasControl = false; - @Override - public void onControllableInsetsChanged(WindowInsetsController controller, - int types) { - if ((types & ime()) != 0 && !hasControl) { - hasControl = true; - controller.controlWindowInsetsAnimation(ime(), -1, - new LinearInterpolator(), null /* cancellationSignal */, - new WindowInsetsAnimationControlListener() { - @Override - public void onReady( - WindowInsetsAnimationController controller, - int types) { - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setDuration(1500); - anim.addUpdateListener(animation - -> controller.setInsetsAndAlpha( - controller.getShownStateInsets(), - (float) animation.getAnimatedValue(), - anim.getAnimatedFraction())); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - controller.finish(true); - } - }); - anim.start(); - } - - @Override - public void onFinished( - WindowInsetsAnimationController controller) { - } - - @Override - public void onCancelled( - WindowInsetsAnimationController controller) { - } - }); - } - } - }); - } - static class Transition { private int mEndBottom; private int mStartBottom; diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index b2e8c378d96d..916c33981171 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -463,7 +463,9 @@ public class NetworkCapabilitiesTest { nc1.setSSID(TEST_SSID); nc2.combineCapabilities(nc1); - assertTrue(TEST_SSID.equals(nc2.getSsid())); + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } // Because they now have the same SSID, the following call should not throw nc2.combineCapabilities(nc1); @@ -601,12 +603,16 @@ public class NetworkCapabilitiesTest { // from nc2. assertFalse(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING)); - assertTrue(TEST_SSID.equals(nc2.getSsid())); + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } nc1.setSSID(DIFFERENT_TEST_SSID); nc2.set(nc1); assertEquals(nc1, nc2); - assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid())); + if (isAtLeastR()) { + assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid())); + } nc1.setUids(uidRange(10, 13)); nc2.set(nc1); // Overwrites, as opposed to combineCapabilities diff --git a/wifi/Android.bp b/wifi/Android.bp index 9d9a495d68ef..d0f1a26f7dbf 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -170,24 +170,23 @@ droidstubs { java_library { name: "framework-wifi-stubs-publicapi", srcs: [":framework-wifi-stubs-srcs-publicapi"], + defaults: ["framework-module-stubs-lib-defaults-publicapi"], + // TODO(b/151134996): remove this sdk_version: "current", - installable: false, } java_library { name: "framework-wifi-stubs-systemapi", srcs: [":framework-wifi-stubs-srcs-systemapi"], - sdk_version: "system_current", libs: ["framework-annotations-lib"], - installable: false, + defaults: ["framework-module-stubs-lib-defaults-systemapi"], } java_library { name: "framework-wifi-stubs-module_libs_api", srcs: [":framework-wifi-stubs-srcs-module_libs_api"], - sdk_version: "module_current", libs: ["framework-annotations-lib"], - installable: false, + defaults: ["framework-module-stubs-lib-defaults-module_libs_api"], } // defaults for tests that need to build against framework-wifi's @hide APIs |