diff options
99 files changed, 3611 insertions, 951 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index e8bcfd2f5bb8..c94cc8ff2612 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2895,7 +2895,11 @@ public class AlarmManagerService extends SystemService { } else { needsPermission = false; lowerQuota = allowWhileIdle; - idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null; + idleOptions = (allowWhileIdle || (alarmClock != null)) + // This avoids exceptions on existing alarms when the app upgrades to + // target S. Note that FGS from pre-S apps isn't restricted anyway. + ? mOptsWithFgs.toBundle() + : null; if (exact) { exactAllowReason = EXACT_ALLOW_REASON_COMPAT; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index 63781ae334d2..5ef6855151cc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -34,6 +34,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; @@ -100,6 +101,10 @@ public final class BatteryController extends RestrictingController { @Override @GuardedBy("mLock") public void prepareForExecutionLocked(JobStatus jobStatus) { + if (!jobStatus.hasPowerConstraint()) { + // Ignore all jobs the controller wouldn't be tracking. + return; + } if (DEBUG) { Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); } @@ -259,6 +264,16 @@ public final class BatteryController extends RestrictingController { } } + @VisibleForTesting + ArraySet<JobStatus> getTrackedJobs() { + return mTrackedTasks; + } + + @VisibleForTesting + ArraySet<JobStatus> getTopStartedJobs() { + return mTopStartedJobs; + } + @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index f97415ca20c8..1769993e0e07 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -20,6 +20,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -66,7 +67,7 @@ public final class NotificationChannelGroup implements Parcelable { private CharSequence mName; private String mDescription; private boolean mBlocked; - private List<NotificationChannel> mChannels = new ArrayList<>(); + private ParceledListSlice<NotificationChannel> mChannels; // Bitwise representation of fields that have been changed by the user private int mUserLockedFields; @@ -100,7 +101,8 @@ public final class NotificationChannelGroup implements Parcelable { } else { mDescription = null; } - in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class); + mChannels = in.readParcelable( + NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class); mBlocked = in.readBoolean(); mUserLockedFields = in.readInt(); } @@ -127,7 +129,7 @@ public final class NotificationChannelGroup implements Parcelable { } else { dest.writeByte((byte) 0); } - dest.writeParcelableList(mChannels, flags); + dest.writeParcelable(mChannels, flags); dest.writeBoolean(mBlocked); dest.writeInt(mUserLockedFields); } @@ -157,7 +159,7 @@ public final class NotificationChannelGroup implements Parcelable { * Returns the list of channels that belong to this group */ public List<NotificationChannel> getChannels() { - return mChannels; + return mChannels == null ? new ArrayList<>() : mChannels.getList(); } /** @@ -191,15 +193,8 @@ public final class NotificationChannelGroup implements Parcelable { /** * @hide */ - public void addChannel(NotificationChannel channel) { - mChannels.add(channel); - } - - /** - * @hide - */ public void setChannels(List<NotificationChannel> channels) { - mChannels = channels; + mChannels = new ParceledListSlice<>(channels); } /** @@ -334,7 +329,7 @@ public final class NotificationChannelGroup implements Parcelable { proto.write(NotificationChannelGroupProto.NAME, mName.toString()); proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription); proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked); - for (NotificationChannel channel : mChannels) { + for (NotificationChannel channel : mChannels.getList()) { channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS); } proto.end(token); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 44dc28d2b0fa..c15b3e0b80c3 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -713,6 +713,15 @@ public class PackageParser { if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) { return null; } + + final ApplicationInfo applicationInfo; + if ((flags & (PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS + | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS)) != 0) { + applicationInfo = generateApplicationInfo(p, flags, state, userId); + } else { + applicationInfo = null; + } + PackageInfo pi = new PackageInfo(); pi.packageName = p.packageName; pi.splitNames = p.splitNames; @@ -773,7 +782,7 @@ public class PackageParser { if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) { continue; } - res[num++] = generateActivityInfo(a, flags, state, userId); + res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo); } } pi.activities = ArrayUtils.trimToSize(res, num); @@ -787,7 +796,7 @@ public class PackageParser { for (int i = 0; i < N; i++) { final Activity a = p.receivers.get(i); if (isMatch(state, a.info, flags)) { - res[num++] = generateActivityInfo(a, flags, state, userId); + res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo); } } pi.receivers = ArrayUtils.trimToSize(res, num); @@ -801,7 +810,7 @@ public class PackageParser { for (int i = 0; i < N; i++) { final Service s = p.services.get(i); if (isMatch(state, s.info, flags)) { - res[num++] = generateServiceInfo(s, flags, state, userId); + res[num++] = generateServiceInfo(s, flags, state, userId, applicationInfo); } } pi.services = ArrayUtils.trimToSize(res, num); @@ -815,7 +824,8 @@ public class PackageParser { for (int i = 0; i < N; i++) { final Provider pr = p.providers.get(i); if (isMatch(state, pr.info, flags)) { - res[num++] = generateProviderInfo(pr, flags, state, userId); + res[num++] = generateProviderInfo(pr, flags, state, userId, + applicationInfo); } } pi.providers = ArrayUtils.trimToSize(res, num); @@ -8216,6 +8226,11 @@ public class PackageParser { @UnsupportedAppUsage public static final ActivityInfo generateActivityInfo(Activity a, int flags, FrameworkPackageUserState state, int userId) { + return generateActivityInfo(a, flags, state, userId, null); + } + + private static ActivityInfo generateActivityInfo(Activity a, int flags, + FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) { if (a == null) return null; if (!checkUseInstalledOrHidden(flags, state, a.owner.applicationInfo)) { return null; @@ -8227,7 +8242,12 @@ public class PackageParser { // Make shallow copies so we can store the metadata safely ActivityInfo ai = new ActivityInfo(a.info); ai.metaData = a.metaData; - ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId); + + if (applicationInfo == null) { + applicationInfo = generateApplicationInfo(a.owner, flags, state, userId); + } + ai.applicationInfo = applicationInfo; + return ai; } @@ -8308,6 +8328,11 @@ public class PackageParser { @UnsupportedAppUsage public static final ServiceInfo generateServiceInfo(Service s, int flags, FrameworkPackageUserState state, int userId) { + return generateServiceInfo(s, flags, state, userId, null); + } + + private static ServiceInfo generateServiceInfo(Service s, int flags, + FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) { if (s == null) return null; if (!checkUseInstalledOrHidden(flags, state, s.owner.applicationInfo)) { return null; @@ -8319,7 +8344,12 @@ public class PackageParser { // Make shallow copies so we can store the metadata safely ServiceInfo si = new ServiceInfo(s.info); si.metaData = s.metaData; - si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId); + + if (applicationInfo == null) { + applicationInfo = generateApplicationInfo(s.owner, flags, state, userId); + } + si.applicationInfo = applicationInfo; + return si; } @@ -8406,13 +8436,18 @@ public class PackageParser { @UnsupportedAppUsage public static final ProviderInfo generateProviderInfo(Provider p, int flags, FrameworkPackageUserState state, int userId) { + return generateProviderInfo(p, flags, state, userId, null); + } + + private static ProviderInfo generateProviderInfo(Provider p, int flags, + FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) { if (p == null) return null; if (!checkUseInstalledOrHidden(flags, state, p.owner.applicationInfo)) { return null; } if (!copyNeeded(flags, p.owner, state, p.metaData, userId) && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0 - || p.info.uriPermissionPatterns == null)) { + || p.info.uriPermissionPatterns == null)) { updateApplicationInfo(p.info.applicationInfo, flags, state); return p.info; } @@ -8422,7 +8457,12 @@ public class PackageParser { if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) { pi.uriPermissionPatterns = null; } - pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId); + + if (applicationInfo == null) { + applicationInfo = generateApplicationInfo(p.owner, flags, state, userId); + } + pi.applicationInfo = applicationInfo; + return pi; } diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java index cbd806617a69..598170dfcb25 100644 --- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java @@ -26,6 +26,10 @@ import static android.hardware.camera2.marshal.MarshalHelpers.getPrimitiveTypeCl import java.lang.reflect.Array; import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; import java.util.ArrayList; /** @@ -51,28 +55,36 @@ public class MarshalQueryableArray<T> implements MarshalQueryable<T> { return new PrimitiveArrayFiller() { @Override public void fillArray(Object arr, int size, ByteBuffer buffer) { - buffer.asIntBuffer().get(int[].class.cast(arr), 0, size); + IntBuffer ib = buffer.asIntBuffer().get(int[].class.cast(arr), 0, size); + // Update buffer position since the IntBuffer has independent position. + buffer.position(buffer.position() + ib.position() * Integer.BYTES); } }; } else if (componentType == float.class) { return new PrimitiveArrayFiller() { @Override public void fillArray(Object arr, int size, ByteBuffer buffer) { - buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size); + FloatBuffer fb = + buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size); + buffer.position(buffer.position() + fb.position() * Float.BYTES); } }; } else if (componentType == long.class) { return new PrimitiveArrayFiller() { @Override public void fillArray(Object arr, int size, ByteBuffer buffer) { - buffer.asLongBuffer().get(long[].class.cast(arr), 0, size); + LongBuffer lb = + buffer.asLongBuffer().get(long[].class.cast(arr), 0, size); + buffer.position(buffer.position() + lb.position() * Long.BYTES); } }; } else if (componentType == double.class) { return new PrimitiveArrayFiller() { @Override public void fillArray(Object arr, int size, ByteBuffer buffer) { - buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size); + DoubleBuffer db = + buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size); + buffer.position(buffer.position() + db.position() * Double.BYTES); } }; } else if (componentType == byte.class) { diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 6049199fc217..195cf82bc36e 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -86,7 +86,12 @@ public class FeatureFlagUtils { /** Flag to enable/disable guest mode UX changes as mentioned in b/214031645 * @hide */ - public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes"; + public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes"; + + /** Support Clear Calling feature. + * @hide + */ + public static final String SETTINGS_ENABLE_CLEAR_CALLING = "settings_enable_clear_calling"; private static final Map<String, String> DEFAULT_FLAGS; @@ -116,6 +121,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true"); DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true"); + DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false"); } private static final Set<String> PERSISTENT_FLAGS; diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 6a0ec3368f19..c198098cb6ff 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -90,8 +90,10 @@ public class InsetsState implements Parcelable { ITYPE_IME, ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR, - ITYPE_LOCAL_NAVIGATION_BAR_1, - ITYPE_LOCAL_NAVIGATION_BAR_2 + ITYPE_LEFT_GENERIC_OVERLAY, + ITYPE_TOP_GENERIC_OVERLAY, + ITYPE_RIGHT_GENERIC_OVERLAY, + ITYPE_BOTTOM_GENERIC_OVERLAY }) public @interface InternalInsetsType {} @@ -135,10 +137,12 @@ public class InsetsState implements Parcelable { public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21; /** Additional types for local insets. **/ - public static final int ITYPE_LOCAL_NAVIGATION_BAR_1 = 22; - public static final int ITYPE_LOCAL_NAVIGATION_BAR_2 = 23; + public static final int ITYPE_LEFT_GENERIC_OVERLAY = 22; + public static final int ITYPE_TOP_GENERIC_OVERLAY = 23; + public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 24; + public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 25; - static final int LAST_TYPE = ITYPE_LOCAL_NAVIGATION_BAR_2; + static final int LAST_TYPE = ITYPE_BOTTOM_GENERIC_OVERLAY; public static final int SIZE = LAST_TYPE + 1; // Derived types @@ -698,8 +702,12 @@ public class InsetsState implements Parcelable { if ((types & Type.NAVIGATION_BARS) != 0) { result.add(ITYPE_NAVIGATION_BAR); result.add(ITYPE_EXTRA_NAVIGATION_BAR); - result.add(ITYPE_LOCAL_NAVIGATION_BAR_1); - result.add(ITYPE_LOCAL_NAVIGATION_BAR_2); + } + if ((types & Type.GENERIC_OVERLAYS) != 0) { + result.add(ITYPE_LEFT_GENERIC_OVERLAY); + result.add(ITYPE_TOP_GENERIC_OVERLAY); + result.add(ITYPE_RIGHT_GENERIC_OVERLAY); + result.add(ITYPE_BOTTOM_GENERIC_OVERLAY); } if ((types & Type.CAPTION_BAR) != 0) { result.add(ITYPE_CAPTION_BAR); @@ -740,9 +748,12 @@ public class InsetsState implements Parcelable { return Type.STATUS_BARS; case ITYPE_NAVIGATION_BAR: case ITYPE_EXTRA_NAVIGATION_BAR: - case ITYPE_LOCAL_NAVIGATION_BAR_1: - case ITYPE_LOCAL_NAVIGATION_BAR_2: return Type.NAVIGATION_BARS; + case ITYPE_LEFT_GENERIC_OVERLAY: + case ITYPE_TOP_GENERIC_OVERLAY: + case ITYPE_RIGHT_GENERIC_OVERLAY: + case ITYPE_BOTTOM_GENERIC_OVERLAY: + return Type.GENERIC_OVERLAYS; case ITYPE_CAPTION_BAR: return Type.CAPTION_BAR; case ITYPE_IME: @@ -861,10 +872,14 @@ public class InsetsState implements Parcelable { return "ITYPE_CLIMATE_BAR"; case ITYPE_EXTRA_NAVIGATION_BAR: return "ITYPE_EXTRA_NAVIGATION_BAR"; - case ITYPE_LOCAL_NAVIGATION_BAR_1: - return "ITYPE_LOCAL_NAVIGATION_BAR_1"; - case ITYPE_LOCAL_NAVIGATION_BAR_2: - return "ITYPE_LOCAL_NAVIGATION_BAR_2"; + case ITYPE_LEFT_GENERIC_OVERLAY: + return "ITYPE_LEFT_GENERIC_OVERLAY"; + case ITYPE_TOP_GENERIC_OVERLAY: + return "ITYPE_TOP_GENERIC_OVERLAY"; + case ITYPE_RIGHT_GENERIC_OVERLAY: + return "ITYPE_RIGHT_GENERIC_OVERLAY"; + case ITYPE_BOTTOM_GENERIC_OVERLAY: + return "ITYPE_BOTTOM_GENERIC_OVERLAY"; default: return "ITYPE_UNKNOWN_" + type; } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index c846175699f2..c1dddbee408f 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -1423,9 +1423,11 @@ public final class WindowInsets { static final int DISPLAY_CUTOUT = 1 << 7; - static final int LAST = 1 << 8; - static final int SIZE = 9; - static final int WINDOW_DECOR = LAST; + static final int WINDOW_DECOR = 1 << 8; + + static final int GENERIC_OVERLAYS = 1 << 9; + static final int LAST = GENERIC_OVERLAYS; + static final int SIZE = 10; static int indexOf(@InsetsType int type) { switch (type) { @@ -1447,6 +1449,8 @@ public final class WindowInsets { return 7; case WINDOW_DECOR: return 8; + case GENERIC_OVERLAYS: + return 9; default: throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST," + " type=" + type); @@ -1482,6 +1486,9 @@ public final class WindowInsets { if ((types & WINDOW_DECOR) != 0) { result.append("windowDecor |"); } + if ((types & GENERIC_OVERLAYS) != 0) { + result.append("genericOverlays |"); + } if (result.length() > 0) { result.delete(result.length() - 2, result.length()); } @@ -1494,7 +1501,8 @@ public final class WindowInsets { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR, - SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT}) + SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT, + GENERIC_OVERLAYS}) public @interface InsetsType { } @@ -1586,7 +1594,7 @@ public final class WindowInsets { * {@link #navigationBars()}, but not {@link #ime()}. */ public static @InsetsType int systemBars() { - return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR; + return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | GENERIC_OVERLAYS; } /** diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index dd990e75e3cc..63d42c0ca915 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2367,6 +2367,14 @@ public interface WindowManager extends ViewManager { public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010; /** + * Flag to allow this window to have unrestricted gesture exclusion. + * + * @see View#setSystemGestureExclusionRects(List) + * @hide + */ + public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020; + + /** * Never animate position changes of the window. * * {@hide} @@ -2586,6 +2594,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED, PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS, SYSTEM_FLAG_SHOW_FOR_ALL_USERS, + PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION, PRIVATE_FLAG_NO_MOVE_ANIMATION, PRIVATE_FLAG_COMPATIBLE_WINDOW, PRIVATE_FLAG_SYSTEM_ERROR, @@ -2633,6 +2642,10 @@ public interface WindowManager extends ViewManager { equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS, name = "SHOW_FOR_ALL_USERS"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION, + equals = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION, + name = "UNRESTRICTED_GESTURE_EXCLUSION"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_NO_MOVE_ANIMATION, equals = PRIVATE_FLAG_NO_MOVE_ANIMATION, name = "NO_MOVE_ANIMATION"), diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7439b2f0921f..5e99d6c41029 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6498,6 +6498,13 @@ <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" android:protectionLevel="signature|privileged" /> + <!-- Allows an app to set gesture exclusion without restrictions on the vertical extent of the + exclusions (see {@link android.view.View#setSystemGestureExclusionRects}). + @hide + --> + <permission android:name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION" + android:protectionLevel="signature|privileged|recents" /> + <!-- @SystemApi Allows TV input apps and TV apps to use TIS extension interfaces for domain-specific features. <p>Protection level: signature|privileged|vendorPrivileged diff --git a/libs/WindowManager/OWNERS b/libs/WindowManager/OWNERS index 780e4c1632f7..2c61df96eb03 100644 --- a/libs/WindowManager/OWNERS +++ b/libs/WindowManager/OWNERS @@ -1,6 +1,3 @@ set noparent include /services/core/java/com/android/server/wm/OWNERS - -# Give submodule owners in shell resource approval -per-file Shell/res*/*/*.xml = hwwang@google.com, lbill@google.com, madym@google.com diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS new file mode 100644 index 000000000000..4b125904004a --- /dev/null +++ b/libs/WindowManager/Shell/OWNERS @@ -0,0 +1,4 @@ +xutan@google.com + +# Give submodule owners in shell resource approval +per-file res*/*/*.xml = hwwang@google.com, lbill@google.com, madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java deleted file mode 100644 index 73fd6931066d..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell; - -import com.android.wm.shell.common.annotations.ExternalThread; - -import java.io.PrintWriter; - -/** - * An entry point into the shell for dumping shell internal state and running adb commands. - * - * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}. - */ -@ExternalThread -public interface ShellCommandHandler { - /** - * Dumps the shell state. - */ - void dump(PrintWriter pw); - - /** - * Handles a shell command. - */ - boolean handleCommand(final String[] args, PrintWriter pw); -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java deleted file mode 100644 index d7010b174744..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell; - -import com.android.wm.shell.common.annotations.ExternalThread; - -/** - * An entry point into the shell for initializing shell internal state. - */ -@ExternalThread -public interface ShellInit { - /** - * Initializes the shell state. - */ - void init(); -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 586eab07649f..f85f9d63a827 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -29,10 +29,8 @@ import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; -import com.android.wm.shell.ShellCommandHandler; -import com.android.wm.shell.ShellCommandHandlerImpl; -import com.android.wm.shell.ShellInit; -import com.android.wm.shell.ShellInitImpl; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.TaskViewFactoryController; @@ -624,13 +622,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static ShellInit provideShellInit(ShellInitImpl impl) { - return impl.asShellInit(); - } - - @WMSingleton - @Provides - static ShellInitImpl provideShellInitImpl(DisplayController displayController, + static ShellInit provideShellInitImpl( + ShellController shellController, + DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, @@ -648,7 +642,8 @@ public abstract class WMShellBaseModule { Transitions transitions, StartingWindowController startingWindow, @ShellMainThread ShellExecutor mainExecutor) { - return new ShellInitImpl(displayController, + return new ShellInit(shellController, + displayController, displayImeController, displayInsetsController, dragAndDropController, @@ -668,19 +663,10 @@ public abstract class WMShellBaseModule { mainExecutor); } - /** - * Note, this is only optional because we currently pass this to the SysUI component scope and - * for non-primary users, we may inject a null-optional for that dependency. - */ @WMSingleton @Provides - static Optional<ShellCommandHandler> provideShellCommandHandler(ShellCommandHandlerImpl impl) { - return Optional.of(impl.asShellCommandHandler()); - } - - @WMSingleton - @Provides - static ShellCommandHandlerImpl provideShellCommandHandlerImpl( + static ShellCommandHandler provideShellCommandHandlerImpl( + ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<SplitScreenController> splitScreenOptional, @@ -689,9 +675,9 @@ public abstract class WMShellBaseModule { Optional<HideDisplayCutoutController> hideDisplayCutout, Optional<RecentTasksController> recentTasksOptional, @ShellMainThread ShellExecutor mainExecutor) { - return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer, - splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, - recentTasksOptional, mainExecutor); + return new ShellCommandHandler(shellController, shellTaskOrganizer, + kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional, + hideDisplayCutout, recentTasksOptional, mainExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java index c5f7c19518a7..0427efb30b9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell; +package com.android.wm.shell.sysui; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; @@ -34,8 +35,8 @@ import java.util.Optional; * * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}. */ -public final class ShellCommandHandlerImpl { - private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName(); +public final class ShellCommandHandler { + private static final String TAG = ShellCommandHandler.class.getSimpleName(); private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<Pip> mPipOptional; @@ -45,9 +46,9 @@ public final class ShellCommandHandlerImpl { private final ShellTaskOrganizer mShellTaskOrganizer; private final KidsModeTaskOrganizer mKidsModeTaskOrganizer; private final ShellExecutor mMainExecutor; - private final HandlerImpl mImpl = new HandlerImpl(); - public ShellCommandHandlerImpl( + public ShellCommandHandler( + ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<SplitScreenController> splitScreenOptional, @@ -64,14 +65,12 @@ public final class ShellCommandHandlerImpl { mOneHandedOptional = oneHandedOptional; mHideDisplayCutout = hideDisplayCutout; mMainExecutor = mainExecutor; - } - - public ShellCommandHandler asShellCommandHandler() { - return mImpl; + // TODO(238217847): To be removed once the command handler dependencies are inverted + shellController.setShellCommandHandler(this); } /** Dumps WM Shell internal state. */ - private void dump(PrintWriter pw) { + public void dump(PrintWriter pw) { mShellTaskOrganizer.dump(pw, ""); pw.println(); pw.println(); @@ -91,7 +90,7 @@ public final class ShellCommandHandlerImpl { /** Returns {@code true} if command was found and executed. */ - private boolean handleCommand(final String[] args, PrintWriter pw) { + public boolean handleCommand(final String[] args, PrintWriter pw) { if (args.length < 2) { // Argument at position 0 is "WMShell". return false; @@ -164,28 +163,4 @@ public final class ShellCommandHandlerImpl { pw.println(" Sets the position of the side-stage."); return true; } - - private class HandlerImpl implements ShellCommandHandler { - @Override - public void dump(PrintWriter pw) { - try { - mMainExecutor.executeBlocking(() -> ShellCommandHandlerImpl.this.dump(pw)); - } catch (InterruptedException e) { - throw new RuntimeException("Failed to dump the Shell in 2s", e); - } - } - - @Override - public boolean handleCommand(String[] args, PrintWriter pw) { - try { - boolean[] result = new boolean[1]; - mMainExecutor.executeBlocking(() -> { - result[0] = ShellCommandHandlerImpl.this.handleCommand(args, pw); - }); - return result[0]; - } catch (InterruptedException e) { - throw new RuntimeException("Failed to handle Shell command in 2s", e); - } - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index 837acecef56f..618028c1544f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -47,6 +47,9 @@ public class ShellController { private final ShellExecutor mMainExecutor; private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl(); + private ShellInit mShellInit; + private ShellCommandHandler mShellCommandHandler; + private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners = @@ -66,6 +69,24 @@ public class ShellController { } /** + * Sets the init handler to call back to. + * TODO(238217847): This is only exposed this way until we can remove the dependencies from the + * init handler to other classes. + */ + public void setShellInit(ShellInit shellInit) { + mShellInit = shellInit; + } + + /** + * Sets the command handler to call back to. + * TODO(238217847): This is only exposed this way until we can remove the dependencies from the + * command handler to other classes. + */ + public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) { + mShellCommandHandler = shellCommandHandler; + } + + /** * Adds a new configuration listener. The configuration change callbacks are not made in any * particular order. */ @@ -164,6 +185,38 @@ public class ShellController { */ @ExternalThread private class ShellInterfaceImpl implements ShellInterface { + + @Override + public void onInit() { + try { + mMainExecutor.executeBlocking(() -> mShellInit.init()); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to initialize the Shell in 2s", e); + } + } + + @Override + public void dump(PrintWriter pw) { + try { + mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw)); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to dump the Shell in 2s", e); + } + } + + @Override + public boolean handleCommand(String[] args, PrintWriter pw) { + try { + boolean[] result = new boolean[1]; + mMainExecutor.executeBlocking(() -> { + result[0] = mShellCommandHandler.handleCommand(args, pw); + }); + return result[0]; + } catch (InterruptedException e) { + throw new RuntimeException("Failed to handle Shell command in 2s", e); + } + } + @Override public void onConfigurationChanged(Configuration newConfiguration) { mMainExecutor.execute(() -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java index 6694e441084b..2619b37b67d8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell; +package com.android.wm.shell.sysui; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT; @@ -26,13 +26,13 @@ import android.util.Pair; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; @@ -53,8 +53,8 @@ import java.util.Optional; * The entry point implementation into the shell for initializing shell internal state. Classes * which need to setup on start should inject an instance of this class and add an init callback. */ -public class ShellInitImpl { - private static final String TAG = ShellInitImpl.class.getSimpleName(); +public class ShellInit { + private static final String TAG = ShellInit.class.getSimpleName(); private final DisplayController mDisplayController; private final DisplayImeController mDisplayImeController; @@ -75,12 +75,12 @@ public class ShellInitImpl { private final Optional<RecentTasksController> mRecentTasks; private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional; - private final InitImpl mImpl = new InitImpl(); // An ordered list of init callbacks to be made once shell is first started private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>(); private boolean mHasInitialized; - public ShellInitImpl( + public ShellInit( + ShellController shellController, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, @@ -117,10 +117,8 @@ public class ShellInitImpl { mTransitions = transitions; mMainExecutor = mainExecutor; mStartingWindow = startingWindow; - } - - public ShellInit asShellInit() { - return mImpl; + // TODO(238217847): To be removed once the init dependencies are inverted + shellController.setShellInit(this); } private void legacyInit() { @@ -210,16 +208,4 @@ public class ShellInitImpl { mHasInitialized = true; } - - @ExternalThread - private class InitImpl implements ShellInit { - @Override - public void init() { - try { - mMainExecutor.executeBlocking(ShellInitImpl.this::init); - } catch (InterruptedException e) { - throw new RuntimeException("Failed to initialize the Shell in 2s", e); - } - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java index a15ce5d2b816..254c253b0042 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java @@ -18,15 +18,32 @@ package com.android.wm.shell.sysui; import android.content.res.Configuration; +import java.io.PrintWriter; + /** * General interface for notifying the Shell of common SysUI events like configuration or keyguard * changes. - * - * TODO: Move ShellInit and ShellCommandHandler into this interface */ public interface ShellInterface { /** + * Initializes the shell state. + */ + default void onInit() {} + + /** + * Dumps the shell state. + */ + default void dump(PrintWriter pw) {} + + /** + * Handles a shell command. + */ + default boolean handleCommand(final String[] args, PrintWriter pw) { + return false; + } + + /** * Notifies the Shell that the configuration has changed. */ default void onConfigurationChanged(Configuration newConfiguration) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index c6bbb027c8e6..9869d2e53979 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -27,8 +27,8 @@ import android.hardware.input.InputManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; +import android.view.Choreographer; import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputEvent; @@ -97,7 +97,7 @@ class DragResizeInputListener implements AutoCloseable { e.rethrowFromSystemServer(); } - mInputEventReceiver = new TaskResizeInputEventReceiver(mInputChannel, mHandler.getLooper()); + mInputEventReceiver = new TaskResizeInputEventReceiver(mInputChannel, mHandler); mCallback = callback; } @@ -154,10 +154,6 @@ class DragResizeInputListener implements AutoCloseable { } catch (RemoteException e) { e.rethrowFromSystemServer(); } - - // This marks all relevant components have handled the previous resize event and can take - // the next one now. - mInputEventReceiver.onHandledLastResizeEvent(); } @Override @@ -171,28 +167,42 @@ class DragResizeInputListener implements AutoCloseable { } private class TaskResizeInputEventReceiver extends InputEventReceiver { - private boolean mWaitingForLastResizeEventHandled; + private final Choreographer mChoreographer; + private final Runnable mConsumeBatchEventRunnable; + private boolean mConsumeBatchEventScheduled; - private TaskResizeInputEventReceiver(InputChannel inputChannel, Looper looper) { - super(inputChannel, looper); - } + private TaskResizeInputEventReceiver(InputChannel inputChannel, Handler handler) { + super(inputChannel, handler.getLooper()); + + final Choreographer[] choreographer = new Choreographer[1]; + handler.runWithScissors( + () -> choreographer[0] = Choreographer.getInstance(), 0); + mChoreographer = choreographer[0]; - private void onHandledLastResizeEvent() { - mWaitingForLastResizeEventHandled = false; - consumeBatchedInputEvents(-1); + mConsumeBatchEventRunnable = () -> { + mConsumeBatchEventScheduled = false; + if (consumeBatchedInputEvents(mChoreographer.getFrameTimeNanos())) { + // If we consumed a batch here, we want to go ahead and schedule the + // consumption of batched input events on the next frame. Otherwise, we would + // wait until we have more input events pending and might get starved by other + // things occurring in the process. + scheduleConsumeBatchEvent(); + } + }; } @Override public void onBatchedInputEventPending(int source) { - // InputEventReceiver keeps continuous move events in a batched event until explicitly - // consuming it or an incompatible event shows up (likely an up event in this case). We - // continue to keep move events in the next batched event until we receive a geometry - // update so that we don't put too much pressure on the framework with excessive number - // of input events if it can't handle them fast enough. It's more responsive to always - // resize the task to the latest received coordinates. - if (!mWaitingForLastResizeEventHandled) { - consumeBatchedInputEvents(-1); + scheduleConsumeBatchEvent(); + } + + private void scheduleConsumeBatchEvent() { + if (mConsumeBatchEventScheduled) { + return; } + mChoreographer.postCallback( + Choreographer.CALLBACK_INPUT, mConsumeBatchEventRunnable, null); + mConsumeBatchEventScheduled = true; } @Override @@ -211,14 +221,12 @@ class DragResizeInputListener implements AutoCloseable { mDragPointerId = e.getPointerId(0); mCallback.onDragResizeStart( calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0)); - mWaitingForLastResizeEventHandled = false; break; } case MotionEvent.ACTION_MOVE: { int dragPointerIndex = e.findPointerIndex(mDragPointerId); mCallback.onDragResizeMove( e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); - mWaitingForLastResizeEventHandled = true; break; } case MotionEvent.ACTION_UP: @@ -226,7 +234,6 @@ class DragResizeInputListener implements AutoCloseable { int dragPointerIndex = e.findPointerIndex(mDragPointerId); mCallback.onDragResizeEnd( e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); - mWaitingForLastResizeEventHandled = false; mDragPointerId = -1; break; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java index ace8d365c7af..219e5ab6c651 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java @@ -38,6 +38,8 @@ import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; @@ -54,8 +56,9 @@ import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class ShellInitImplTest extends ShellTestCase { +public class ShellInitTest extends ShellTestCase { + @Mock private ShellController mShellController; @Mock private DisplayController mDisplayController; @Mock private DisplayImeController mDisplayImeController; @Mock private DisplayInsetsController mDisplayInsetsController; @@ -75,12 +78,12 @@ public class ShellInitImplTest extends ShellTestCase { @Mock private StartingWindowController mStartingWindow; @Mock private ShellExecutor mMainExecutor; - private ShellInitImpl mImpl; + private ShellInit mImpl; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mImpl = new ShellInitImpl(mDisplayController, mDisplayImeController, + mImpl = new ShellInit(mShellController, mDisplayController, mDisplayImeController, mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer, mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional, mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController, diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java index 190e1cc083bd..fba4249260ef 100644 --- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -142,6 +142,14 @@ public class GpsNetInitiatedHandler { public int textEncoding; } + /** Callbacks for Emergency call events. */ + public interface EmergencyCallCallback { + /** Callback invoked when an emergency call starts */ + void onEmergencyCallStart(int subId); + /** Callback invoked when an emergency call ends */ + void onEmergencyCallEnd(); + } + private class EmergencyCallListener extends TelephonyCallback implements TelephonyCallback.OutgoingEmergencyCallListener, TelephonyCallback.CallStateListener { @@ -152,6 +160,7 @@ public class GpsNetInitiatedHandler { int subscriptionId) { mIsInEmergencyCall = true; if (DEBUG) Log.d(TAG, "onOutgoingEmergencyCall(): inEmergency = " + getInEmergency()); + mEmergencyCallCallback.onEmergencyCallStart(subscriptionId); } @Override @@ -163,6 +172,7 @@ public class GpsNetInitiatedHandler { if (mIsInEmergencyCall) { mCallEndElapsedRealtimeMillis = SystemClock.elapsedRealtime(); mIsInEmergencyCall = false; + mEmergencyCallCallback.onEmergencyCallEnd(); } } } @@ -180,8 +190,11 @@ public class GpsNetInitiatedHandler { */ private Notification.Builder mNiNotificationBuilder; + private final EmergencyCallCallback mEmergencyCallCallback; + public GpsNetInitiatedHandler(Context context, INetInitiatedListener netInitiatedListener, + EmergencyCallCallback emergencyCallCallback, boolean isSuplEsEnabled) { mContext = context; @@ -190,6 +203,7 @@ public class GpsNetInitiatedHandler { } else { mNetInitiatedListener = netInitiatedListener; } + mEmergencyCallCallback = emergencyCallCallback; setSuplEsEnabled(isSuplEsEnabled); mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index ec82ccf2022e..5dc34b9db594 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<androidx.constraintlayout.motion.widget.MotionLayout +<com.android.systemui.util.NoRemeasureMotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/split_shade_status_bar" @@ -32,10 +32,37 @@ <androidx.constraintlayout.widget.Guideline android:layout_width="wrap_content" android:layout_height="wrap_content" - android:id="@+id/center" - app:layout_constraintGuide_percent="0.5" + android:id="@+id/begin_guide" + android:orientation="vertical" + app:layout_constraintGuide_begin="0dp"/> + + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/end_guide" + android:orientation="vertical" + app:layout_constraintGuide_end="0dp" + /> + + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/center_left" android:orientation="vertical" /> + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/center_right" + android:orientation="vertical" /> + + <androidx.constraintlayout.widget.Barrier + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/barrier" + app:barrierDirection="start" + app:constraint_referenced_ids="statusIcons,privacy_container" /> + <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" android:layout_width="wrap_content" @@ -44,18 +71,25 @@ android:paddingStart="@dimen/status_bar_left_clock_starting_padding" android:paddingEnd="@dimen/status_bar_left_clock_end_padding" android:singleLine="true" + android:textDirection="locale" android:textAppearance="@style/TextAppearance.QS.Status" + android:transformPivotX="0sp" + android:transformPivotY="20sp" + android:scaleX="1" + android:scaleY="1" /> - <com.android.systemui.statusbar.policy.DateView + <com.android.systemui.statusbar.policy.VariableDateView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="0dp" android:layout_gravity="start|center_vertical" android:gravity="center_vertical" android:singleLine="true" + android:textDirection="locale" android:textAppearance="@style/TextAppearance.QS.Status" - app:datePattern="@string/abbrev_wday_month_day_no_year_alarm" + app:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm" + app:shortDatePattern="@string/abbrev_month_day_no_year" /> <include @@ -81,7 +115,7 @@ app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" android:paddingEnd="@dimen/signal_cluster_battery_padding" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintStart_toEndOf="@id/carrier_group" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" app:layout_constraintTop_toTopOf="@id/clock" @@ -92,8 +126,9 @@ <com.android.systemui.battery.BatteryMeterView android:id="@+id/batteryRemainingIcon" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" + app:layout_constrainedWidth="true" app:textAppearance="@style/TextAppearance.QS.Status" app:layout_constraintStart_toEndOf="@id/statusIcons" app:layout_constraintEnd_toEndOf="parent" @@ -104,13 +139,18 @@ <FrameLayout android:id="@+id/privacy_container" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="@dimen/large_screen_shade_header_min_height" android:gravity="center" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toEndOf="@id/end_guide" app:layout_constraintTop_toTopOf="@id/date" app:layout_constraintBottom_toBottomOf="@id/date" > <include layout="@layout/ongoing_privacy_chip"/> </FrameLayout> -</androidx.constraintlayout.motion.widget.MotionLayout>
\ No newline at end of file + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:id="@+id/space" + /> +</com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 022a6b2f4da2..0cafa355d847 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -553,7 +553,7 @@ <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen> <dimen name="qs_panel_elevation">4dp</dimen> <dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen> - <dimen name="qs_panel_padding_top">48dp</dimen> + <dimen name="qs_panel_padding_top">80dp</dimen> <dimen name="qs_data_usage_text_size">14sp</dimen> <dimen name="qs_data_usage_usage_text_size">36sp</dimen> @@ -564,6 +564,8 @@ <dimen name="qs_footer_icon_size">20dp</dimen> <dimen name="qs_header_row_min_height">48dp</dimen> + <dimen name="qs_header_non_clickable_element_height">24dp</dimen> + <dimen name="qs_footer_padding">20dp</dimen> <dimen name="qs_security_footer_height">88dp</dimen> <dimen name="qs_security_footer_single_line_height">48dp</dimen> diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml index 0e833265c15f..0fac76d11fbc 100644 --- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml +++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml @@ -25,20 +25,109 @@ <KeyFrameSet> <!-- These positions are to prevent visual movement of @id/date --> <KeyPosition - app:keyPositionType="pathRelative" + app:keyPositionType="deltaRelative" app:percentX="0" + app:percentY="0" app:framePosition="49" + app:percentWidth="1" + app:percentHeight="1" + app:curveFit="linear" app:motionTarget="@id/date" /> <KeyPosition - app:keyPositionType="pathRelative" + app:keyPositionType="deltaRelative" app:percentX="1" + app:percentY="0.51" app:framePosition="51" + app:percentWidth="1" + app:percentHeight="1" + app:curveFit="linear" app:motionTarget="@id/date" /> <KeyAttribute app:motionTarget="@id/date" + app:framePosition="30" + android:alpha="0" + /> + <KeyAttribute + app:motionTarget="@id/date" + app:framePosition="70" + android:alpha="0" + /> + <KeyPosition + app:keyPositionType="pathRelative" + app:percentX="0" + app:percentY="0" + app:framePosition="0" + app:curveFit="linear" + app:motionTarget="@id/statusIcons" /> + <KeyPosition + app:keyPositionType="pathRelative" + app:percentX="0" + app:percentY="0" + app:framePosition="50" + app:curveFit="linear" + app:motionTarget="@id/statusIcons" /> + <KeyPosition + app:keyPositionType="deltaRelative" + app:percentX="1" + app:percentY="0.51" + app:framePosition="51" + app:curveFit="linear" + app:motionTarget="@id/statusIcons" /> + <KeyAttribute + app:motionTarget="@id/statusIcons" + app:framePosition="30" + android:alpha="0" + /> + <KeyAttribute + app:motionTarget="@id/statusIcons" + app:framePosition="70" + android:alpha="0" + /> + <KeyPosition + app:keyPositionType="deltaRelative" + app:percentX="0" + app:percentY="0" app:framePosition="50" + app:percentWidth="1" + app:percentHeight="1" + app:curveFit="linear" + app:motionTarget="@id/batteryRemainingIcon" /> + <KeyPosition + app:keyPositionType="deltaRelative" + app:percentX="1" + app:percentY="0.51" + app:framePosition="51" + app:percentWidth="1" + app:percentHeight="1" + app:curveFit="linear" + app:motionTarget="@id/batteryRemainingIcon" /> + <KeyAttribute + app:motionTarget="@id/batteryRemainingIcon" + app:framePosition="30" android:alpha="0" /> + <KeyAttribute + app:motionTarget="@id/batteryRemainingIcon" + app:framePosition="70" + android:alpha="0" + /> + <KeyPosition + app:motionTarget="@id/carrier_group" + app:percentX="1" + app:percentY="0.51" + app:framePosition="51" + app:percentWidth="1" + app:percentHeight="1" + app:curveFit="linear" + app:keyPositionType="deltaRelative" /> + <KeyAttribute + app:motionTarget="@id/carrier_group" + app:framePosition="0" + android:alpha="0" /> + <KeyAttribute + app:motionTarget="@id/carrier_group" + app:framePosition="70" + android:alpha="0" /> </KeyFrameSet> </Transition> diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml index 89090513ea37..cdbf8ab0be41 100644 --- a/packages/SystemUI/res/xml/large_screen_shade_header.xml +++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml @@ -29,7 +29,12 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/date" + app:layout_constraintHorizontal_bias="0" /> + <Transform + android:scaleX="1" + android:scaleY="1" + /> </Constraint> <Constraint @@ -47,9 +52,38 @@ <Constraint android:id="@+id/carrier_group"> + <Layout + app:layout_constraintWidth_min="48dp" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constrainedWidth="true" + android:layout_gravity="end|center_vertical" + android:layout_marginStart="8dp" + app:layout_constraintStart_toEndOf="@id/date" + app:layout_constraintEnd_toStartOf="@id/statusIcons" + app:layout_constraintTop_toTopOf="@id/clock" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + <PropertySet + android:alpha="1" + /> + </Constraint> + + <Constraint + android:id="@+id/statusIcons"> + <Layout + app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" + android:layout_width="wrap_content" + android:layout_height="@dimen/large_screen_shade_header_min_height" + app:layout_constraintStart_toEndOf="@id/carrier_group" + app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" + app:layout_constraintTop_toTopOf="@id/clock" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> <PropertySet android:alpha="1" - app:customFloatValue="1" /> </Constraint> @@ -64,6 +98,9 @@ app:layout_constraintTop_toTopOf="@id/clock" app:layout_constraintBottom_toBottomOf="parent" /> + <PropertySet + android:alpha="1" + /> </Constraint> <Constraint @@ -75,6 +112,7 @@ app:layout_constraintTop_toTopOf="@id/date" app:layout_constraintBottom_toBottomOf="@id/date" app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon" + app:layout_constraintHorizontal_bias="1" /> </Constraint> diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index c5b4c5d776b9..ee0c4fb6bab8 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -26,22 +26,27 @@ <Layout android:layout_width="wrap_content" android:layout_height="0dp" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toStartOf="@id/begin_guide" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/date" app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_chainStyle="packed" /> + <Transform + android:scaleX="1" + android:scaleY="1" + /> </Constraint> <Constraint android:id="@+id/date"> <Layout - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="0dp" + app:layout_constrainedWidth="true" app:layout_constraintStart_toEndOf="@id/clock" - app:layout_constraintEnd_toStartOf="@id/carrier_group" + app:layout_constraintEnd_toStartOf="@id/barrier" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0" @@ -50,17 +55,41 @@ <Constraint android:id="@+id/statusIcons"> + <Layout + android:layout_width="0dp" + android:layout_height="@dimen/qs_header_non_clickable_element_height" + app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" + app:layout_constraintStart_toEndOf="@id/date" + app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" + app:layout_constraintTop_toTopOf="@id/date" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> </Constraint> <Constraint - android:id="@+id/batteryRemainingIcon" > + android:id="@+id/batteryRemainingIcon"> + <Layout + android:layout_width="wrap_content" + android:layout_height="@dimen/qs_header_non_clickable_element_height" + app:layout_constrainedWidth="true" + app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" + app:layout_constraintStart_toEndOf="@id/statusIcons" + app:layout_constraintEnd_toEndOf="@id/end_guide" + app:layout_constraintTop_toTopOf="@id/date" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> </Constraint> <Constraint android:id="@+id/carrier_group"> - <CustomAttribute - app:attributeName="alpha" - app:customFloatValue="0" + <Layout + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + /> + <PropertySet + android:alpha="0" /> </Constraint> @@ -69,9 +98,11 @@ <Layout android:layout_width="wrap_content" android:layout_height="0dp" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/date" + app:layout_constraintEnd_toEndOf="@id/end_guide" app:layout_constraintTop_toTopOf="@id/date" app:layout_constraintBottom_toBottomOf="@id/date" + app:layout_constraintHorizontal_bias="1" /> </Constraint> diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml new file mode 100644 index 000000000000..f39e6bd65b86 --- /dev/null +++ b/packages/SystemUI/res/xml/qs_header_new.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 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. + --> + +<ConstraintSet + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/qs_header_constraint" +> + + <Constraint + android:id="@+id/privacy_container"> + <Layout + android:layout_width="wrap_content" + android:layout_height="@dimen/large_screen_shade_header_min_height" + app:layout_constraintEnd_toEndOf="@id/end_guide" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/carrier_group" + app:layout_constraintHorizontal_bias="1" + /> + </Constraint> + + <Constraint + android:id="@+id/clock"> + <Layout + android:layout_width="wrap_content" + android:layout_height="@dimen/large_screen_shade_header_min_height" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/privacy_container" + app:layout_constraintBottom_toTopOf="@id/date" + app:layout_constraintEnd_toStartOf="@id/carrier_group" + app:layout_constraintHorizontal_bias="0" + /> + <Transform + android:scaleX="2.4" + android:scaleY="2.4" + /> + </Constraint> + + <Constraint + android:id="@+id/date"> + <Layout + android:layout_width="0dp" + android:layout_height="@dimen/qs_header_non_clickable_element_height" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/space" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/clock" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="spread_inside" + /> + </Constraint> + + <Constraint + android:id="@+id/carrier_group"> + <Layout + app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" + android:minHeight="@dimen/large_screen_shade_header_min_height" + app:layout_constraintWidth_min="48dp" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/clock" + app:layout_constraintTop_toBottomOf="@id/privacy_container" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="1" + app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon" + /> + <PropertySet + android:alpha="1" + /> + </Constraint> + + <Constraint + android:id="@+id/statusIcons"> + <Layout + android:layout_width="0dp" + android:layout_height="@dimen/qs_header_non_clickable_element_height" + app:layout_constrainedWidth="true" + app:layout_constraintStart_toEndOf="@id/space" + app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" + app:layout_constraintTop_toTopOf="@id/date" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + </Constraint> + + <Constraint + android:id="@+id/batteryRemainingIcon"> + <Layout + android:layout_width="wrap_content" + android:layout_height="@dimen/qs_header_non_clickable_element_height" + app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" + app:layout_constraintStart_toEndOf="@id/statusIcons" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/date" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + </Constraint> + + + <Constraint + android:id="@id/space"> + <Layout + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/date" + app:layout_constraintEnd_toStartOf="@id/statusIcons" + /> + </Constraint> +</ConstraintSet>
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt index e22386e78359..b4955d28f84b 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt @@ -257,6 +257,8 @@ class AnimatableClockView @JvmOverloads constructor( ) } + private val glyphFilter: GlyphCallback? = null // Add text animation tweak here. + /** * Set text style with an optional animation. * @@ -288,6 +290,7 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) + textAnimator?.glyphFilter = glyphFilter } else { // when the text animator is set, update its start values onTextAnimatorInitialized = Runnable { @@ -301,6 +304,7 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) + textAnimator?.glyphFilter = glyphFilter } } } diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt index 336101528450..ade89af81bd7 100644 --- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt +++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt @@ -22,12 +22,14 @@ import android.animation.TimeInterpolator import android.animation.ValueAnimator import android.graphics.Canvas import android.graphics.Typeface +import android.graphics.fonts.Font import android.text.Layout import android.util.SparseArray private const val TAG_WGHT = "wght" private const val DEFAULT_ANIMATION_DURATION: Long = 300 +typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit /** * This class provides text animation between two styles. * @@ -74,6 +76,59 @@ class TextAnimator( }) } + sealed class PositionedGlyph { + + /** + * Mutable X coordinate of the glyph position relative from drawing offset. + */ + var x: Float = 0f + + /** + * Mutable Y coordinate of the glyph position relative from the baseline. + */ + var y: Float = 0f + + /** + * Mutable text size of the glyph in pixels. + */ + var textSize: Float = 0f + + /** + * Mutable color of the glyph. + */ + var color: Int = 0 + + /** + * Immutable character offset in the text that the current font run start. + */ + abstract var runStart: Int + protected set + + /** + * Immutable run length of the font run. + */ + abstract var runLength: Int + protected set + + /** + * Immutable glyph index of the font run. + */ + abstract var glyphIndex: Int + protected set + + /** + * Immutable font instance for this font run. + */ + abstract var font: Font + protected set + + /** + * Immutable glyph ID for this glyph. + */ + abstract var glyphId: Int + protected set + } + private val typefaceCache = SparseArray<Typeface?>() fun updateLayout(layout: Layout) { @@ -84,6 +139,57 @@ class TextAnimator( return animator.isRunning } + /** + * GlyphFilter applied just before drawing to canvas for tweaking positions and text size. + * + * This callback is called for each glyphs just before drawing the glyphs. This function will + * be called with the intrinsic position, size, color, glyph ID and font instance. You can + * mutate the position, size and color for tweaking animations. + * Do not keep the reference of passed glyph object. The interpolator reuses that object for + * avoiding object allocations. + * + * Details: + * The text is drawn with font run units. The font run is a text segment that draws with the + * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text + * that current glyph is in. Once the font run is determined, the system will convert characters + * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and + * {@code glyphIndex} is the offset of the converted glyph array. Please note that the + * {@code glyphIndex} is not a character index, because the character will not be converted to + * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be + * composed from multiple characters. + * + * Here is an example of font runs: "fin. 終わり" + * + * Characters : f i n . _ 終 わ り + * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A + * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf --> + * runStart = 0, runLength = 5 runStart = 5, runLength = 3 + * Glyph IDs : 194 48 7 8 4367 1039 1002 + * Glyph Index: 0 1 2 3 0 1 2 + * + * In this example, the "fi" is converted into ligature form, thus the single glyph ID is + * assigned for two characters, f and i. + * + * Example: + * ``` + * private val glyphFilter: GlyphCallback = { glyph, progress -> + * val index = glyph.runStart + * val i = glyph.glyphIndex + * val moveAmount = 1.3f + * val sign = (-1 + 2 * ((i + index) % 2)) + * val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f + * + * // You can modify (x, y) coordinates, textSize and color during animation. + * glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress + * glyph.y += glyph.y * sign * moveAmount * turnProgress + * glyph.x += glyph.x * sign * moveAmount * turnProgress + * } + * ``` + */ + var glyphFilter: GlyphCallback? + get() = textInterpolator.glyphFilter + set(value) { textInterpolator.glyphFilter = value } + fun draw(c: Canvas) = textInterpolator.draw(c) /** diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt index 5d5797cbbb3d..20dbe29efb35 100644 --- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt +++ b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt @@ -89,8 +89,11 @@ class TextInterpolator( private var lines = listOf<Line>() private val fontInterpolator = FontInterpolator() - // Recycling object for glyph drawing. Will be extended for the longest font run if needed. - private val tmpDrawPaint = TextPaint() + // Recycling object for glyph drawing and tweaking. + private val tmpPaint = TextPaint() + private val tmpPaintForGlyph by lazy { TextPaint() } + private val tmpGlyph by lazy { MutablePositionedGlyph() } + // Will be extended for the longest font run if needed. private var tmpPositionArray = FloatArray(20) /** @@ -206,8 +209,8 @@ class TextInterpolator( } else if (progress == 1f) { basePaint.set(targetPaint) } else { - lerp(basePaint, targetPaint, progress, tmpDrawPaint) - basePaint.set(tmpDrawPaint) + lerp(basePaint, targetPaint, progress, tmpPaint) + basePaint.set(tmpPaint) } lines.forEach { line -> @@ -231,7 +234,7 @@ class TextInterpolator( * @param canvas a canvas. */ fun draw(canvas: Canvas) { - lerp(basePaint, targetPaint, progress, tmpDrawPaint) + lerp(basePaint, targetPaint, progress, tmpPaint) lines.forEachIndexed { lineNo, line -> line.runs.forEach { run -> canvas.save() @@ -241,7 +244,7 @@ class TextInterpolator( canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) run.fontRuns.forEach { fontRun -> - drawFontRun(canvas, run, fontRun, tmpDrawPaint) + drawFontRun(canvas, run, fontRun, tmpPaint) } } finally { canvas.restore() @@ -330,24 +333,82 @@ class TextInterpolator( } } + private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() { + override var runStart: Int = 0 + public set + override var runLength: Int = 0 + public set + override var glyphIndex: Int = 0 + public set + override lateinit var font: Font + public set + override var glyphId: Int = 0 + public set + } + + var glyphFilter: GlyphCallback? = null + // Draws single font run. private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) { var arrayIndex = 0 + val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress) + + val glyphFilter = glyphFilter + if (glyphFilter == null) { + for (i in run.start until run.end) { + tmpPositionArray[arrayIndex++] = + MathUtils.lerp(line.baseX[i], line.targetX[i], progress) + tmpPositionArray[arrayIndex++] = + MathUtils.lerp(line.baseY[i], line.targetY[i], progress) + } + c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint) + return + } + + tmpGlyph.font = font + tmpGlyph.runStart = run.start + tmpGlyph.runLength = run.end - run.start + + tmpPaintForGlyph.set(paint) + var prevStart = run.start + for (i in run.start until run.end) { - tmpPositionArray[arrayIndex++] = - MathUtils.lerp(line.baseX[i], line.targetX[i], progress) - tmpPositionArray[arrayIndex++] = - MathUtils.lerp(line.baseY[i], line.targetY[i], progress) + tmpGlyph.glyphId = line.glyphIds[i] + tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) + tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) + tmpGlyph.textSize = paint.textSize + tmpGlyph.color = paint.color + + glyphFilter(tmpGlyph, progress) + + if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) { + tmpPaintForGlyph.textSize = tmpGlyph.textSize + tmpPaintForGlyph.color = tmpGlyph.color + + c.drawGlyphs( + line.glyphIds, + prevStart, + tmpPositionArray, + 0, + i - prevStart, + font, + tmpPaintForGlyph) + prevStart = i + arrayIndex = 0 + } + + tmpPositionArray[arrayIndex++] = tmpGlyph.x + tmpPositionArray[arrayIndex++] = tmpGlyph.y } c.drawGlyphs( line.glyphIds, - run.start, + prevStart, tmpPositionArray, 0, - run.length, - fontInterpolator.lerp(run.baseFont, run.targetFont, progress), - paint) + run.end - prevStart, + font, + tmpPaintForGlyph) } private fun updatePositionsAndFonts( diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index 24fcf15d7a11..e9ca0fdbb929 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -96,7 +96,6 @@ public abstract class SystemUIInitializer { .setSplitScreen(mWMComponent.getSplitScreen()) .setOneHanded(mWMComponent.getOneHanded()) .setBubbles(mWMComponent.getBubbles()) - .setShellCommandHandler(mWMComponent.getShellCommandHandler()) .setTaskViewFactory(mWMComponent.getTaskViewFactory()) .setTransitions(mWMComponent.getTransitions()) .setStartingSurface(mWMComponent.getStartingSurface()) @@ -112,7 +111,6 @@ public abstract class SystemUIInitializer { .setSplitScreen(Optional.ofNullable(null)) .setOneHanded(Optional.ofNullable(null)) .setBubbles(Optional.ofNullable(null)) - .setShellCommandHandler(Optional.ofNullable(null)) .setTaskViewFactory(Optional.ofNullable(null)) .setTransitions(new ShellTransitions() {}) .setDisplayAreaHelper(Optional.ofNullable(null)) diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt index cccd3a482ef0..81da80233d42 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt @@ -36,7 +36,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shared.system.ActivityManagerKt.isInForeground import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.statusbar.phone.PanelViewController +import com.android.systemui.shade.PanelViewController import com.android.systemui.statusbar.policy.KeyguardStateController import java.util.concurrent.Executor import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index fd7680f463c0..029cabb0bc0b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -37,7 +37,6 @@ import com.android.systemui.unfold.FoldStateLoggingProvider; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.unfold.UnfoldLatencyTracker; import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; -import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; @@ -96,9 +95,6 @@ public interface SysUIComponent { Builder setTaskViewFactory(Optional<TaskViewFactory> t); @BindsInstance - Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump); - - @BindsInstance Builder setTransitions(ShellTransitions t); @BindsInstance diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index e4c0325d4d5a..78a45f9d3310 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -23,8 +23,8 @@ import androidx.annotation.Nullable; import com.android.systemui.SystemUIInitializerFactory; import com.android.systemui.tv.TvWMComponent; -import com.android.wm.shell.ShellCommandHandler; -import com.android.wm.shell.ShellInit; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; @@ -75,17 +75,24 @@ public interface WMComponent { * Initializes all the WMShell components before starting any of the SystemUI components. */ default void init() { - getShellInit().init(); + // TODO(238217847): To be removed once the dependencies are inverted and ShellController can + // inject these classes directly, otherwise, it's currently needed to ensure that these + // classes are created and set on the controller before onInit() is called + getShellInit(); + getShellCommandHandler(); + getShell().onInit(); } @WMSingleton - ShellInit getShellInit(); + ShellInterface getShell(); + // TODO(238217847): To be removed once ShellController can inject ShellInit directly @WMSingleton - Optional<ShellCommandHandler> getShellCommandHandler(); + ShellInit getShellInit(); + // TODO(238217847): To be removed once ShellController can inject ShellCommandHandler directly @WMSingleton - ShellInterface getShell(); + ShellCommandHandler getShellCommandHandler(); @WMSingleton Optional<OneHanded> getOneHanded(); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java index b9436f96c74f..9c22dc61e67b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java @@ -25,7 +25,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler; import com.android.systemui.dreams.touch.DreamTouchHandler; -import com.android.systemui.statusbar.phone.PanelViewController; +import com.android.systemui.shade.PanelViewController; import com.android.wm.shell.animation.FlingAnimationUtils; import javax.inject.Named; diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 46173b872817..65711edd6e24 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -63,6 +63,9 @@ public class Flags { public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS = new BooleanFlag(109, false); + public static final BooleanFlag FSI_REQUIRES_KEYGUARD = + new BooleanFlag(110, false, true); + /***************************************/ // 200 - keyguard/lockscreen @@ -116,7 +119,7 @@ public class Flags { new BooleanFlag(500, true); public static final BooleanFlag COMBINED_QS_HEADERS = - new BooleanFlag(501, false); + new BooleanFlag(501, false, true); public static final ResourceBooleanFlag PEOPLE_TILE = new ResourceBooleanFlag(502, R.bool.flag_conversations); @@ -130,7 +133,7 @@ public class Flags { @Deprecated public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, true); - public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false); + public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false, true); public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER = new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher); diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt new file mode 100644 index 000000000000..e360ec20bd9b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 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.shade + +import androidx.constraintlayout.widget.ConstraintSet + +typealias ConstraintChange = ConstraintSet.() -> Unit + +operator fun ConstraintChange?.plus(other: ConstraintChange?): ConstraintChange? { + // Prevent wrapping + if (this == null) return other + if (other == null) return this + else return { + this@plus() + other() + } +} + +/** + * Contains all changes that need to be performed to the different [ConstraintSet] in + * [LargeScreenShadeHeaderController]. + */ +data class ConstraintsChanges( + val qqsConstraintsChanges: ConstraintChange? = null, + val qsConstraintsChanges: ConstraintChange? = null, + val largeScreenConstraintsChanges: ConstraintChange? = null +) { + operator fun plus(other: ConstraintsChanges) = ConstraintsChanges( + qqsConstraintsChanges + other.qqsConstraintsChanges, + qsConstraintsChanges + other.qsConstraintsChanges, + largeScreenConstraintsChanges + other.largeScreenConstraintsChanges + ) +} + +/** + * Determines [ConstraintChanges] for [LargeScreenShadeHeaderController] based on configurations. + * + * Given that the number of different scenarios is not that large, having specific methods instead + * of a full map between state and [ConstraintSet] was preferred. + */ +interface CombinedShadeHeadersConstraintManager { + /** + * Changes for when the visibility of the privacy chip changes + */ + fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges + + /** + * Changes for situations with no top center cutout (there may be a corner cutout) + */ + fun emptyCutoutConstraints(): ConstraintsChanges + + /** + * Changes to incorporate side insets due to rounded corners/corner cutouts + */ + fun edgesGuidelinesConstraints( + cutoutStart: Int, + paddingStart: Int, + cutoutEnd: Int, + paddingEnd: Int + ): ConstraintsChanges + + /** + * Changes for situations with top center cutout (in this case, there are no corner cutouts). + */ + fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt new file mode 100644 index 000000000000..4063af3cbc36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 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.shade + +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.R +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent + +/** + * Standard implementation of [CombinedShadeHeadersConstraintManager]. + */ +@CentralSurfacesComponent.CentralSurfacesScope +object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstraintManager { + + override fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges { + val constraintAlpha = if (visible) 0f else 1f + return ConstraintsChanges( + qqsConstraintsChanges = { + setAlpha(R.id.statusIcons, constraintAlpha) + setAlpha(R.id.batteryRemainingIcon, constraintAlpha) + } + ) + } + + override fun emptyCutoutConstraints(): ConstraintsChanges { + return ConstraintsChanges( + qqsConstraintsChanges = { + connect(R.id.date, ConstraintSet.END, R.id.barrier, ConstraintSet.START) + createBarrier( + R.id.barrier, + ConstraintSet.START, + 0, + R.id.statusIcons, + R.id.privacy_container + ) + connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END) + connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END) + constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT) + } + ) + } + + override fun edgesGuidelinesConstraints( + cutoutStart: Int, + paddingStart: Int, + cutoutEnd: Int, + paddingEnd: Int + ): ConstraintsChanges { + val change: ConstraintChange = { + setGuidelineBegin(R.id.begin_guide, Math.max(cutoutStart - paddingStart, 0)) + setGuidelineEnd(R.id.end_guide, Math.max(cutoutEnd - paddingEnd, 0)) + } + return ConstraintsChanges( + qqsConstraintsChanges = change, + qsConstraintsChanges = change + ) + } + + override fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges { + val centerStart = if (!rtl) R.id.center_left else R.id.center_right + val centerEnd = if (!rtl) R.id.center_right else R.id.center_left + // Use guidelines to block the center cutout area. + return ConstraintsChanges( + qqsConstraintsChanges = { + setGuidelineBegin(centerStart, offsetFromEdge) + setGuidelineEnd(centerEnd, offsetFromEdge) + connect(R.id.date, ConstraintSet.END, centerStart, ConstraintSet.START) + connect( + R.id.statusIcons, + ConstraintSet.START, + centerEnd, + ConstraintSet.END + ) + connect( + R.id.privacy_container, + ConstraintSet.START, + centerEnd, + ConstraintSet.END + ) + constrainWidth(R.id.statusIcons, 0) + }, + qsConstraintsChanges = { + setGuidelineBegin(centerStart, offsetFromEdge) + setGuidelineEnd(centerEnd, offsetFromEdge) + connect( + R.id.privacy_container, + ConstraintSet.START, + centerEnd, + ConstraintSet.END + ) + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt new file mode 100644 index 000000000000..5793105e481e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2021 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.shade + +import android.annotation.IdRes +import android.app.StatusBarManager +import android.content.res.Configuration +import android.os.Trace +import android.os.Trace.TRACE_TAG_APP +import android.util.Pair +import android.view.View +import android.view.WindowInsets +import android.widget.TextView +import androidx.annotation.VisibleForTesting +import androidx.constraintlayout.motion.widget.MotionLayout +import com.android.settingslib.Utils +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.animation.ShadeInterpolation +import com.android.systemui.battery.BatteryMeterView +import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.qs.ChipVisibilityListener +import com.android.systemui.qs.HeaderPrivacyIconsController +import com.android.systemui.qs.carrier.QSCarrierGroup +import com.android.systemui.qs.carrier.QSCarrierGroupController +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider +import com.android.systemui.statusbar.phone.StatusBarIconController +import com.android.systemui.statusbar.phone.StatusIconContainer +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope +import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER +import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.VariableDateView +import com.android.systemui.statusbar.policy.VariableDateViewController +import com.android.systemui.util.ViewController +import java.io.PrintWriter +import javax.inject.Inject +import javax.inject.Named + +/** + * Controller for QS header on Large Screen width (large screen + landscape). + * + * Additionally, this serves as the staging ground for the combined QS headers. A single + * [MotionLayout] that changes constraints depending on the configuration and can animate the + * expansion of the headers in small screen portrait. + * + * [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the + * [MotionLayout] has 2 transitions: + * * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait + * handheld device configuration. + * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all + * other configurations + */ +@CentralSurfacesScope +class LargeScreenShadeHeaderController @Inject constructor( + @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View, + private val statusBarIconController: StatusBarIconController, + private val privacyIconsController: HeaderPrivacyIconsController, + private val insetsProvider: StatusBarContentInsetsProvider, + private val configurationController: ConfigurationController, + private val variableDateViewControllerFactory: VariableDateViewController.Factory, + @Named(LARGE_SCREEN_BATTERY_CONTROLLER) + private val batteryMeterViewController: BatteryMeterViewController, + private val dumpManager: DumpManager, + private val featureFlags: FeatureFlags, + private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, + private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager +) : ViewController<View>(header), Dumpable { + + companion object { + /** IDs for transitions and constraints for the [MotionLayout]. These are only used when + * [Flags.COMBINED_QS_HEADERS] is enabled. + */ + @VisibleForTesting + internal val HEADER_TRANSITION_ID = R.id.header_transition + @VisibleForTesting + internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition + @VisibleForTesting + internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint + @VisibleForTesting + internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint + @VisibleForTesting + internal val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint + + private fun Int.stateToString() = when (this) { + QQS_HEADER_CONSTRAINT -> "QQS Header" + QS_HEADER_CONSTRAINT -> "QS Header" + LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header" + else -> "Unknown state" + } + } + + init { + loadConstraints() + } + + private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS) + + private lateinit var iconManager: StatusBarIconController.TintedIconManager + private lateinit var carrierIconSlots: List<String> + private lateinit var qsCarrierGroupController: QSCarrierGroupController + + private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon) + private val clock: TextView = header.findViewById(R.id.clock) + private val date: TextView = header.findViewById(R.id.date) + private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons) + private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group) + + private var cutoutLeft = 0 + private var cutoutRight = 0 + private var roundedCorners = 0 + private var lastInsets: WindowInsets? = null + + private var qsDisabled = false + private var visible = false + set(value) { + if (field == value) { + return + } + field = value + updateListeners() + } + + /** + * Whether the QQS/QS part of the shade is visible. This is particularly important in + * Lockscreen, as the shade is visible but QS is not. + */ + var qsVisible = false + set(value) { + if (field == value) { + return + } + field = value + onShadeExpandedChanged() + } + + /** + * Whether we are in a configuration with large screen width. In this case, the header is a + * single line. + */ + var largeScreenActive = false + set(value) { + if (field == value) { + return + } + field = value + onHeaderStateChanged() + } + + /** + * Expansion fraction of the QQS/QS shade. This is not the expansion between QQS <-> QS. + */ + var shadeExpandedFraction = -1f + set(value) { + if (visible && field != value) { + header.alpha = ShadeInterpolation.getContentAlpha(value) + field = value + } + } + + /** + * Expansion fraction of the QQS <-> QS animation. + */ + var qsExpandedFraction = -1f + set(value) { + if (visible && field != value) { + field = value + updatePosition() + } + } + + /** + * Current scroll of QS. + */ + var qsScrollY = 0 + set(value) { + if (field != value) { + field = value + updateScrollY() + } + } + + private val insetListener = View.OnApplyWindowInsetsListener { view, insets -> + updateConstraintsForInsets(view as MotionLayout, insets) + lastInsets = WindowInsets(insets) + + view.onApplyWindowInsets(insets) + } + + private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener { + override fun onChipVisibilityRefreshed(visible: Boolean) { + if (header is MotionLayout) { + // If the privacy chip is visible, we hide the status icons and battery remaining + // icon, only in QQS. + val update = combinedShadeHeadersConstraintManager + .privacyChipVisibilityConstraints(visible) + header.updateAllConstraints(update) + } + } + } + + private val configurationControllerListener = + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + if (header !is MotionLayout) { + val left = header.resources.getDimensionPixelSize( + R.dimen.large_screen_shade_header_left_padding + ) + header.setPadding( + left, + header.paddingTop, + header.paddingRight, + header.paddingBottom + ) + } + } + + override fun onDensityOrFontScaleChanged() { + clock.setTextAppearance(R.style.TextAppearance_QS_Status) + date.setTextAppearance(R.style.TextAppearance_QS_Status) + qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers) + if (header is MotionLayout) { + loadConstraints() + lastInsets?.let { updateConstraintsForInsets(header, it) } + } + updateResources() + } + } + + override fun onInit() { + if (header is MotionLayout) { + variableDateViewControllerFactory.create(date as VariableDateView).init() + } + batteryMeterViewController.init() + + // battery settings same as in QS icons + batteryMeterViewController.ignoreTunerUpdates() + batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) + + iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags) + iconManager.setTint( + Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary) + ) + + carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) { + listOf( + header.context.getString(com.android.internal.R.string.status_bar_no_calling), + header.context.getString(com.android.internal.R.string.status_bar_call_strength) + ) + } else { + listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile)) + } + qsCarrierGroupController = qsCarrierGroupControllerBuilder + .setQSCarrierGroup(qsCarrierGroup) + .build() + } + + override fun onViewAttached() { + privacyIconsController.chipVisibilityListener = chipVisibilityListener + if (header is MotionLayout) { + header.setOnApplyWindowInsetsListener(insetListener) + clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> + val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f + v.pivotX = newPivot + } + } + + dumpManager.registerDumpable(this) + configurationController.addCallback(configurationControllerListener) + + updateVisibility() + updateTransition() + } + + override fun onViewDetached() { + privacyIconsController.chipVisibilityListener = null + dumpManager.unregisterDumpable(this::class.java.simpleName) + configurationController.removeCallback(configurationControllerListener) + } + + fun disable(state1: Int, state2: Int, animate: Boolean) { + val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0 + if (disabled == qsDisabled) return + qsDisabled = disabled + updateVisibility() + } + + private fun loadConstraints() { + if (header is MotionLayout) { + // Use resources.getXml instead of passing the resource id due to bug b/205018300 + header.getConstraintSet(QQS_HEADER_CONSTRAINT) + .load(context, resources.getXml(R.xml.qqs_header)) + val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) { + R.xml.qs_header_new + } else { + R.xml.qs_header + } + header.getConstraintSet(QS_HEADER_CONSTRAINT) + .load(context, resources.getXml(qsConstraints)) + header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT) + .load(context, resources.getXml(R.xml.large_screen_shade_header)) + } + } + + private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) { + val cutout = insets.displayCutout + + val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation() + cutoutLeft = sbInsets.first + cutoutRight = sbInsets.second + val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout() + updateQQSPaddings() + // Set these guides as the left/right limits for content that lives in the top row, using + // cutoutLeft and cutoutRight + var changes = combinedShadeHeadersConstraintManager + .edgesGuidelinesConstraints( + if (view.isLayoutRtl) cutoutRight else cutoutLeft, + header.paddingStart, + if (view.isLayoutRtl) cutoutLeft else cutoutRight, + header.paddingEnd + ) + + if (cutout != null) { + val topCutout = cutout.boundingRectTop + if (topCutout.isEmpty || hasCornerCutout) { + changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints() + } else { + changes += combinedShadeHeadersConstraintManager.centerCutoutConstraints( + view.isLayoutRtl, + (view.width - view.paddingLeft - view.paddingRight - topCutout.width()) / 2 + ) + } + } else { + changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints() + } + + view.updateAllConstraints(changes) + } + + private fun updateScrollY() { + if (!largeScreenActive && combinedHeaders) { + header.scrollY = qsScrollY + } + } + + private fun onShadeExpandedChanged() { + if (qsVisible) { + privacyIconsController.startListening() + } else { + privacyIconsController.stopListening() + } + updateVisibility() + updatePosition() + } + + private fun onHeaderStateChanged() { + if (largeScreenActive || combinedHeaders) { + privacyIconsController.onParentVisible() + } else { + privacyIconsController.onParentInvisible() + } + updateVisibility() + updateTransition() + } + + /** + * If not using [combinedHeaders] this should only be visible on large screen. Else, it should + * be visible any time the QQS/QS shade is open. + */ + private fun updateVisibility() { + val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) { + View.GONE + } else if (qsVisible) { + View.VISIBLE + } else { + View.INVISIBLE + } + if (header.visibility != visibility) { + header.visibility = visibility + visible = visibility == View.VISIBLE + } + } + + private fun updateTransition() { + if (!combinedHeaders) { + return + } + header as MotionLayout + if (largeScreenActive) { + header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID) + header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header) + } else { + header.setTransition(HEADER_TRANSITION_ID) + header.transitionToStart() + updatePosition() + updateScrollY() + } + } + + private fun updatePosition() { + if (header is MotionLayout && !largeScreenActive && visible) { + Trace.instantForTrack( + TRACE_TAG_APP, + "LargeScreenHeaderController - updatePosition", + "position: $qsExpandedFraction" + ) + header.progress = qsExpandedFraction + } + } + + private fun updateListeners() { + qsCarrierGroupController.setListening(visible) + if (visible) { + updateSingleCarrier(qsCarrierGroupController.isSingleCarrier) + qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) } + statusBarIconController.addIconGroup(iconManager) + } else { + qsCarrierGroupController.setOnSingleCarrierChangedListener(null) + statusBarIconController.removeIconGroup(iconManager) + } + } + + private fun updateSingleCarrier(singleCarrier: Boolean) { + if (singleCarrier) { + iconContainer.removeIgnoredSlots(carrierIconSlots) + } else { + iconContainer.addIgnoredSlots(carrierIconSlots) + } + } + + private fun updateResources() { + roundedCorners = resources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding) + val padding = resources.getDimensionPixelSize(R.dimen.qs_panel_padding) + header.setPadding(padding, header.paddingTop, padding, header.paddingBottom) + updateQQSPaddings() + } + + private fun updateQQSPaddings() { + if (header is MotionLayout) { + val clockPaddingStart = resources + .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding) + val clockPaddingEnd = resources + .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding) + clock.setPaddingRelative( + clockPaddingStart, + clock.paddingTop, + clockPaddingEnd, + clock.paddingBottom + ) + } + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("visible: $visible") + pw.println("shadeExpanded: $qsVisible") + pw.println("shadeExpandedFraction: $shadeExpandedFraction") + pw.println("active: $largeScreenActive") + pw.println("qsExpandedFraction: $qsExpandedFraction") + pw.println("qsScrollY: $qsScrollY") + if (combinedHeaders) { + header as MotionLayout + pw.println("currentState: ${header.currentState.stateToString()}") + } + } + + private fun MotionLayout.updateConstraints(@IdRes state: Int, update: ConstraintChange) { + val constraints = getConstraintSet(state) + constraints.update() + updateState(state, constraints) + } + + /** + * Updates the [ConstraintSet] for the case of combined headers. + * + * Only non-`null` changes are applied to reduce the number of rebuilding in the [MotionLayout]. + */ + private fun MotionLayout.updateAllConstraints(updates: ConstraintsChanges) { + if (updates.qqsConstraintsChanges != null) { + updateConstraints(QQS_HEADER_CONSTRAINT, updates.qqsConstraintsChanges) + } + if (updates.qsConstraintsChanges != null) { + updateConstraints(QS_HEADER_CONSTRAINT, updates.qsConstraintsChanges) + } + if (updates.largeScreenConstraintsChanges != null) { + updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java index e0997ff0f905..9818af3751a7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java @@ -25,7 +25,6 @@ import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.PanelView; import com.android.systemui.statusbar.phone.TapAgainView; public class NotificationPanelView extends PanelView { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index bab92ba492c9..99d0fe97971a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -189,12 +189,9 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; -import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.PanelView; -import com.android.systemui.statusbar.phone.PanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; @@ -1038,6 +1035,7 @@ public final class NotificationPanelViewController extends PanelViewController { } mTapAgainViewController.init(); + mLargeScreenShadeHeaderController.init(); mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView)); mNotificationPanelUnfoldAnimationController.ifPresent(controller -> controller.setup(mNotificationContainerParent)); @@ -1143,7 +1141,7 @@ public final class NotificationPanelViewController extends PanelViewController { SystemBarUtils.getQuickQsOffsetHeight(mView.getContext()); int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top); - mLargeScreenShadeHeaderController.setActive(mUseLargeScreenShadeHeader); + mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader); mAmbientState.setStackTopMargin(topMargin); mNotificationsQSContainerController.updateResources(); @@ -1716,10 +1714,17 @@ public final class NotificationPanelViewController extends PanelViewController { /** * Animate QS closing by flinging it. * If QS is expanded, it will collapse into QQS and stop. + * If in split shade, it will collapse the whole shade. * * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore. */ public void animateCloseQs(boolean animateAway) { + if (mSplitShadeEnabled) { + collapsePanel( + /* animate= */true, /* delayed= */false, /* speedUpFactor= */1.0f); + return; + } + if (mQsExpansionAnimator != null) { if (!mQsAnimatorExpand) { return; @@ -2417,7 +2422,7 @@ public final class NotificationPanelViewController extends PanelViewController { : getExpandedFraction(); mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); - mLargeScreenShadeHeaderController.setShadeExpanded(mQsVisible); + mLargeScreenShadeHeaderController.setQsVisible(mQsVisible); } private void onStackYChanged(boolean shouldAnimate) { @@ -3392,19 +3397,11 @@ public final class NotificationPanelViewController extends PanelViewController { return mQsExpanded; } - public boolean isQsDetailShowing() { - return mQs.isShowingDetail(); - } - /** Returns whether the QS customizer is currently active. */ public boolean isQsCustomizing() { return mQs.isCustomizing(); } - public void closeQsDetail() { - mQs.closeDetail(); - } - /** Close the QS customizer if it is open. */ public void closeQsCustomizer() { mQs.closeCustomizer(); @@ -3748,6 +3745,8 @@ public final class NotificationPanelViewController extends PanelViewController { final float dozeAmount = dozing ? 1 : 0; mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate); + + updateKeyguardStatusViewAlignment(animate); } public void setPulsing(boolean pulsing) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS index 133711ee5ab2..7dc9dc7efeb7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS @@ -3,6 +3,7 @@ per-file *Notification* = file:../statusbar/notification/OWNERS per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com +per-file *ShadeHeader* = kozynski@google.com, asc@google.com per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java index 45dc943de2f8..1082967d3b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import android.content.Context; import android.content.res.Configuration; @@ -22,6 +22,10 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.FrameLayout; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; + public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = false; public static final String TAG = PanelView.class.getSimpleName(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java index 3a85a3ea6391..229acf4ed061 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @@ -60,7 +60,14 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.stack.AmbientState; +import com.android.systemui.statusbar.phone.BounceInterpolator; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.time.SystemClock; @@ -388,7 +395,7 @@ public abstract class PanelViewController { return Math.abs(yDiff) >= Math.abs(xDiff); } - protected void startExpandMotion(float newX, float newY, boolean startTracking, + public void startExpandMotion(float newX, float newY, boolean startTracking, float expandedHeight) { if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) { beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index c4947ca2dd90..0c6a91fe61f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -59,4 +59,7 @@ class NotifPipelineFlags @Inject constructor( fun removeUnrankedNotifs(): Boolean = featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS) + + fun fullScreenIntentRequiresKeyguard(): Boolean = + featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 535dc6e2b583..a72b3814d5ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.ArrayList; import java.util.List; @@ -58,6 +59,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>(); private final StatusBarStateController mStatusBarStateController; + private final KeyguardStateController mKeyguardStateController; private final NotificationFilter mNotificationFilter; private final ContentResolver mContentResolver; private final PowerManager mPowerManager; @@ -82,6 +84,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter NotificationFilter notificationFilter, BatteryController batteryController, StatusBarStateController statusBarStateController, + KeyguardStateController keyguardStateController, HeadsUpManager headsUpManager, NotificationInterruptLogger logger, @Main Handler mainHandler, @@ -94,6 +97,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter mAmbientDisplayConfiguration = ambientDisplayConfiguration; mNotificationFilter = notificationFilter; mStatusBarStateController = statusBarStateController; + mKeyguardStateController = keyguardStateController; mHeadsUpManager = headsUpManager; mLogger = logger; mFlags = flags; @@ -228,6 +232,28 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } + // Check whether FSI requires the keyguard to be showing. + if (mFlags.fullScreenIntentRequiresKeyguard()) { + + // If notification won't HUN and keyguard is showing, launch the FSI. + if (mKeyguardStateController.isShowing()) { + if (mKeyguardStateController.isOccluded()) { + mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded"); + } else { + // Likely LOCKED_SHADE, but launch FSI anyway + mLogger.logFullscreen(entry, "Keyguard is showing and not occluded"); + } + return true; + } + + // Detect the case determined by b/231322873 to launch FSI while device is in use, + // as blocked by the correct implementation, and report the event. + final int uid = entry.getSbn().getUid(); + android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard"); + mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard"); + return false; + } + // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI. mLogger.logFullscreen(entry, "Expected not to HUN"); return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 2d6d846bf2a2..98404eb0b3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3325,11 +3325,7 @@ public class CentralSurfacesImpl extends CoreStartable implements return true; } if (mNotificationPanelViewController.isQsExpanded()) { - if (mNotificationPanelViewController.isQsDetailShowing()) { - mNotificationPanelViewController.closeQsDetail(); - } else { mNotificationPanelViewController.animateCloseQs(false /* animateAway */); - } return true; } if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt deleted file mode 100644 index 84c8700436cc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (C) 2021 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.phone - -import android.app.StatusBarManager -import android.content.res.Configuration -import android.view.View -import android.widget.TextView -import androidx.constraintlayout.motion.widget.MotionLayout -import com.android.settingslib.Utils -import com.android.systemui.Dumpable -import com.android.systemui.FontSizeUtils -import com.android.systemui.R -import com.android.systemui.animation.ShadeInterpolation -import com.android.systemui.battery.BatteryMeterView -import com.android.systemui.battery.BatteryMeterViewController -import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.qs.ChipVisibilityListener -import com.android.systemui.qs.HeaderPrivacyIconsController -import com.android.systemui.qs.carrier.QSCarrierGroup -import com.android.systemui.qs.carrier.QSCarrierGroupController -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope -import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER -import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER -import com.android.systemui.statusbar.policy.ConfigurationController -import java.io.PrintWriter -import javax.inject.Inject -import javax.inject.Named - -@CentralSurfacesScope -class LargeScreenShadeHeaderController @Inject constructor( - @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View, - private val statusBarIconController: StatusBarIconController, - private val privacyIconsController: HeaderPrivacyIconsController, - private val configurationController: ConfigurationController, - qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, - featureFlags: FeatureFlags, - @Named(LARGE_SCREEN_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController, - dumpManager: DumpManager -) : Dumpable { - - companion object { - private val HEADER_TRANSITION_ID = R.id.header_transition - private val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition - private val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint - private val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint - private val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint - - private fun Int.stateToString() = when (this) { - QQS_HEADER_CONSTRAINT -> "QQS Header" - QS_HEADER_CONSTRAINT -> "QS Header" - LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header" - else -> "Unknown state" - } - } - - private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS) - private val iconManager: StatusBarIconController.TintedIconManager - private val iconContainer: StatusIconContainer - private val carrierIconSlots: List<String> - private val qsCarrierGroupController: QSCarrierGroupController - private val clock: TextView = header.findViewById(R.id.clock) - private val date: TextView = header.findViewById(R.id.date) - private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group) - - private var qsDisabled = false - - private var visible = false - set(value) { - if (field == value) { - return - } - field = value - updateListeners() - } - - var shadeExpanded = false - set(value) { - if (field == value) { - return - } - field = value - onShadeExpandedChanged() - } - - var active = false - set(value) { - if (field == value) { - return - } - field = value - onHeaderStateChanged() - } - - var shadeExpandedFraction = -1f - set(value) { - if (visible && field != value) { - header.alpha = ShadeInterpolation.getContentAlpha(value) - field = value - } - } - - var qsExpandedFraction = -1f - set(value) { - if (visible && field != value) { - field = value - updateVisibility() - updatePosition() - } - } - - var qsScrollY = 0 - set(value) { - if (field != value) { - field = value - updateScrollY() - } - } - - private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener { - override fun onChipVisibilityRefreshed(visible: Boolean) { - if (header is MotionLayout) { - val state = header.getConstraintSet(QQS_HEADER_CONSTRAINT).apply { - setAlpha(R.id.statusIcons, if (visible) 0f else 1f) - setAlpha(R.id.batteryRemainingIcon, if (visible) 0f else 1f) - } - header.updateState(QQS_HEADER_CONSTRAINT, state) - } - } - } - - init { - if (header is MotionLayout) { - val context = header.context - val resources = header.resources - header.getConstraintSet(QQS_HEADER_CONSTRAINT) - .load(context, resources.getXml(R.xml.qqs_header)) - header.getConstraintSet(QS_HEADER_CONSTRAINT) - .load(context, resources.getXml(R.xml.qs_header)) - header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT) - .load(context, resources.getXml(R.xml.large_screen_shade_header)) - privacyIconsController.chipVisibilityListener = chipVisibilityListener - } - - bindConfigurationListener() - - batteryMeterViewController.init() - val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon) - - // battery settings same as in QS icons - batteryMeterViewController.ignoreTunerUpdates() - batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) - - iconContainer = header.findViewById(R.id.statusIcons) - iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags) - iconManager.setTint(Utils.getColorAttrDefaultColor(header.context, - android.R.attr.textColorPrimary)) - - carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) { - listOf( - header.context.getString(com.android.internal.R.string.status_bar_no_calling), - header.context.getString(com.android.internal.R.string.status_bar_call_strength) - ) - } else { - listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile)) - } - qsCarrierGroupController = qsCarrierGroupControllerBuilder - .setQSCarrierGroup(header.findViewById(R.id.carrier_group)) - .build() - - dumpManager.registerDumpable(this) - - updateVisibility() - updateConstraints() - } - - fun disable(state1: Int, state2: Int, animate: Boolean) { - val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0 - if (disabled == qsDisabled) return - qsDisabled = disabled - updateVisibility() - } - - private fun updateScrollY() { - if (!active && combinedHeaders) { - header.scrollY = qsScrollY - } - } - - private fun bindConfigurationListener() { - val listener = object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration?) { - val left = header.resources.getDimensionPixelSize( - R.dimen.large_screen_shade_header_left_padding) - header.setPadding( - left, header.paddingTop, header.paddingRight, header.paddingBottom) - } - override fun onDensityOrFontScaleChanged() { - val qsStatusStyle = R.style.TextAppearance_QS_Status - FontSizeUtils.updateFontSizeFromStyle(clock, qsStatusStyle) - FontSizeUtils.updateFontSizeFromStyle(date, qsStatusStyle) - qsCarrierGroup.updateTextAppearance(qsStatusStyle) - } - } - configurationController.addCallback(listener) - } - - private fun onShadeExpandedChanged() { - if (shadeExpanded) { - privacyIconsController.startListening() - } else { - privacyIconsController.stopListening() - } - updateVisibility() - updatePosition() - } - - private fun onHeaderStateChanged() { - if (active || combinedHeaders) { - privacyIconsController.onParentVisible() - } else { - privacyIconsController.onParentInvisible() - } - updateVisibility() - updateConstraints() - } - - private fun updateVisibility() { - val visibility = if (!active && !combinedHeaders || qsDisabled) { - View.GONE - } else if (shadeExpanded) { - View.VISIBLE - } else { - View.INVISIBLE - } - if (header.visibility != visibility) { - header.visibility = visibility - visible = visibility == View.VISIBLE - } - } - - private fun updateConstraints() { - if (!combinedHeaders) { - return - } - header as MotionLayout - if (active) { - header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID) - } else { - header.setTransition(HEADER_TRANSITION_ID) - header.transitionToStart() - updatePosition() - updateScrollY() - } - } - - private fun updatePosition() { - if (header is MotionLayout && !active && visible) { - header.setProgress(qsExpandedFraction) - } - } - - private fun updateListeners() { - qsCarrierGroupController.setListening(visible) - if (visible) { - updateSingleCarrier(qsCarrierGroupController.isSingleCarrier) - qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) } - statusBarIconController.addIconGroup(iconManager) - } else { - qsCarrierGroupController.setOnSingleCarrierChangedListener(null) - statusBarIconController.removeIconGroup(iconManager) - } - } - - private fun updateSingleCarrier(singleCarrier: Boolean) { - if (singleCarrier) { - iconContainer.removeIgnoredSlots(carrierIconSlots) - } else { - iconContainer.addIgnoredSlots(carrierIconSlots) - } - } - - override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println("visible: $visible") - pw.println("shadeExpanded: $shadeExpanded") - pw.println("shadeExpandedFraction: $shadeExpandedFraction") - pw.println("active: $active") - pw.println("qsExpandedFraction: $qsExpandedFraction") - pw.println("qsScrollY: $qsScrollY") - if (combinedHeaders) { - header as MotionLayout - pw.println("currentState: ${header.currentState.stateToString()}") - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index f5462bc0fba5..c850d4f9c56b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -123,7 +123,7 @@ class StatusBarContentInsetsProvider @Inject constructor( val point = Point() context.display.getRealSize(point) - return topBounds.left <= 0 || topBounds.right >= point.y + return topBounds.left <= 0 || topBounds.right >= point.x } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 4bbd69b91a0a..4c239314f70f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -399,7 +399,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, return true; } - if (sbn.getNotification().fullScreenIntent != null) { + if (sbn.getNotification().fullScreenIntent != null + && !mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()) { // we don't allow head-up on the lockscreen (unless there's a // "showWhenLocked" activity currently showing) if // the potential HUN has a fullscreen intent diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index 84b279760f36..64b04e93e69c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -22,6 +22,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.keyguard.LockIconViewController; import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.shade.LargeScreenShadeHeaderController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -35,7 +36,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule; import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks; import com.android.systemui.statusbar.phone.CentralSurfacesImpl; -import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController; import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule; import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 88e985f9eeaf..b60739164a19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -35,6 +35,8 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.privacy.OngoingPrivacyChip; +import com.android.systemui.shade.CombinedShadeHeadersConstraintManager; +import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; @@ -179,6 +181,14 @@ public abstract class StatusBarViewModule { /** */ @Provides @CentralSurfacesComponent.CentralSurfacesScope + public static CombinedShadeHeadersConstraintManager + provideCombinedShadeHeadersConstraintManager() { + return CombinedShadeHeadersConstraintManagerImpl.INSTANCE; + } + + /** */ + @Provides + @CentralSurfacesComponent.CentralSurfacesScope public static OngoingPrivacyChip getSplitShadeOngoingPrivacyChip( @Named(LARGE_SCREEN_SHADE_HEADER) View header) { return header.findViewById(R.id.privacy_chip); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index 337ffdf46560..86e74564fba0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -155,6 +155,7 @@ public class BrightnessMirrorController .inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false); mToggleSliderController = setMirrorLayout(); mStatusBarWindow.addView(mBrightnessMirror, index); + updateResources(); for (int i = 0; i < mBrightnessMirrorListeners.size(); i++) { mBrightnessMirrorListeners.valueAt(i).onBrightnessMirrorReinflated(mBrightnessMirror); diff --git a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt new file mode 100644 index 000000000000..3095d80d1630 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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.util + +import android.content.Context +import android.util.AttributeSet +import android.view.Choreographer +import androidx.constraintlayout.motion.widget.MotionLayout + +/** + * [MotionLayout] that avoids remeasuring with the same inputs in the same frame. + * + * This is important when this view is the child of a view that performs more than one measure pass + * (e.g. [LinearLayout] or [ConstraintLayout]). In those cases, if this view is measured with the + * same inputs in the same frame, we use the last result. + */ +class NoRemeasureMotionLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet?, + defStyle: Int = 0 +) : MotionLayout(context, attrs, defStyle) { + + private var lastWidthSpec: Int? = null + private var lastHeightSpec: Int? = null + private var lastFrame: Long? = null + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if ( + lastWidthSpec == widthMeasureSpec && + lastHeightSpec == heightMeasureSpec && + Choreographer.getMainThreadInstance()?.frameTime == lastFrame + ) { + setMeasuredDimension(measuredWidth, measuredHeight) + return + } + traceSection("NoRemeasureMotionLayout - measure") { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + lastWidthSpec = widthMeasureSpec + lastHeightSpec = heightMeasureSpec + lastFrame = Choreographer.getMainThreadInstance()?.frameTime + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 83b0022b9f53..12597e0896b1 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -54,7 +54,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; -import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.nano.WmShellTraceProto; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; @@ -107,7 +106,6 @@ public final class WMShell extends CoreStartable private final Optional<Pip> mPipOptional; private final Optional<SplitScreen> mSplitScreenOptional; private final Optional<OneHanded> mOneHandedOptional; - private final Optional<ShellCommandHandler> mShellCommandHandler; private final CommandQueue mCommandQueue; private final ConfigurationController mConfigurationController; @@ -130,7 +128,6 @@ public final class WMShell extends CoreStartable Optional<Pip> pipOptional, Optional<SplitScreen> splitScreenOptional, Optional<OneHanded> oneHandedOptional, - Optional<ShellCommandHandler> shellCommandHandler, CommandQueue commandQueue, ConfigurationController configurationController, KeyguardStateController keyguardStateController, @@ -154,7 +151,6 @@ public final class WMShell extends CoreStartable mOneHandedOptional = oneHandedOptional; mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; - mShellCommandHandler = shellCommandHandler; mUserInfoController = userInfoController; mSysUiMainExecutor = sysUiMainExecutor; } @@ -325,8 +321,7 @@ public final class WMShell extends CoreStartable @Override public void dump(PrintWriter pw, String[] args) { // Handle commands if provided - if (mShellCommandHandler.isPresent() - && mShellCommandHandler.get().handleCommand(args, pw)) { + if (mShell.handleCommand(args, pw)) { return; } // Handle logging commands if provided @@ -334,8 +329,7 @@ public final class WMShell extends CoreStartable return; } // Dump WMShell stuff here if no commands were handled - mShellCommandHandler.ifPresent( - shellCommandHandler -> shellCommandHandler.dump(pw)); + mShell.dump(pw); } @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt index e42d537596c5..603cf3bcef74 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt @@ -18,6 +18,7 @@ package com.android.keyguard import android.graphics.Bitmap import android.graphics.Canvas +import android.graphics.Color import android.graphics.Typeface import android.graphics.fonts.Font import android.graphics.fonts.FontFamily @@ -194,6 +195,128 @@ class TextInterpolatorTest : SysuiTestCase() { assertThat(expected.sameAs(actual)).isTrue() } + + @Test + fun testGlyphCallback_Empty() { + val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) + + val interp = TextInterpolator(layout).apply { + glyphFilter = { glyph, progress -> + } + } + interp.basePaint.set(START_PAINT) + interp.onBasePaintModified() + + interp.targetPaint.set(END_PAINT) + interp.onTargetPaintModified() + + // Just after created TextInterpolator, it should have 0 progress. + val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) + val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) + .toBitmap(BMP_WIDTH, BMP_HEIGHT) + + assertThat(expected.sameAs(actual)).isTrue() + } + + @Test + fun testGlyphCallback_Xcoordinate() { + val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) + + val interp = TextInterpolator(layout).apply { + glyphFilter = { glyph, progress -> + glyph.x += 30f + } + } + interp.basePaint.set(START_PAINT) + interp.onBasePaintModified() + + interp.targetPaint.set(END_PAINT) + interp.onTargetPaintModified() + + // Just after created TextInterpolator, it should have 0 progress. + val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) + val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) + .toBitmap(BMP_WIDTH, BMP_HEIGHT) + + // The glyph position was modified by callback, so the bitmap should not be the same. + // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. + assertThat(expected.sameAs(actual)).isFalse() + } + + @Test + fun testGlyphCallback_Ycoordinate() { + val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) + + val interp = TextInterpolator(layout).apply { + glyphFilter = { glyph, progress -> + glyph.y += 30f + } + } + interp.basePaint.set(START_PAINT) + interp.onBasePaintModified() + + interp.targetPaint.set(END_PAINT) + interp.onTargetPaintModified() + + // Just after created TextInterpolator, it should have 0 progress. + val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) + val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) + .toBitmap(BMP_WIDTH, BMP_HEIGHT) + + // The glyph position was modified by callback, so the bitmap should not be the same. + // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. + assertThat(expected.sameAs(actual)).isFalse() + } + + @Test + fun testGlyphCallback_TextSize() { + val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) + + val interp = TextInterpolator(layout).apply { + glyphFilter = { glyph, progress -> + glyph.textSize += 10f + } + } + interp.basePaint.set(START_PAINT) + interp.onBasePaintModified() + + interp.targetPaint.set(END_PAINT) + interp.onTargetPaintModified() + + // Just after created TextInterpolator, it should have 0 progress. + val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) + val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) + .toBitmap(BMP_WIDTH, BMP_HEIGHT) + + // The glyph position was modified by callback, so the bitmap should not be the same. + // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. + assertThat(expected.sameAs(actual)).isFalse() + } + + @Test + fun testGlyphCallback_Color() { + val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL) + + val interp = TextInterpolator(layout).apply { + glyphFilter = { glyph, progress -> + glyph.color = Color.RED + } + } + interp.basePaint.set(START_PAINT) + interp.onBasePaintModified() + + interp.targetPaint.set(END_PAINT) + interp.onTargetPaintModified() + + // Just after created TextInterpolator, it should have 0 progress. + val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT) + val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL) + .toBitmap(BMP_WIDTH, BMP_HEIGHT) + + // The glyph position was modified by callback, so the bitmap should not be the same. + // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps. + assertThat(expected.sameAs(actual)).isFalse() + } } private fun Layout.toBitmap(width: Int, height: Int) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt new file mode 100644 index 000000000000..0ce9056dc1d1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2022 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.shade + +import android.testing.AndroidTestingRunner +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { + + private lateinit var qqsConstraint: ConstraintSet + private lateinit var qsConstraint: ConstraintSet + private lateinit var largeScreenConstraint: ConstraintSet + + @Before + fun setUp() { + qqsConstraint = ConstraintSet().apply { + load(context, context.resources.getXml(R.xml.qqs_header)) + } + qsConstraint = ConstraintSet().apply { + load(context, context.resources.getXml(R.xml.qs_header_new)) + } + largeScreenConstraint = ConstraintSet().apply { + load(context, context.resources.getXml(R.xml.large_screen_shade_header)) + } + } + + @Test + fun testEdgeElementsAlignedWithGuide_qqs() { + with(qqsConstraint) { + assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(R.id.begin_guide) + assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f) + + assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd) + .isEqualTo(R.id.end_guide) + assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias) + .isEqualTo(1f) + + assertThat(getConstraint(R.id.privacy_container).layout.endToEnd) + .isEqualTo(R.id.end_guide) + assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias) + .isEqualTo(1f) + } + } + + @Test + fun testClockScale() { + with(qqsConstraint.getConstraint(R.id.clock)) { + assertThat(transform.scaleX).isEqualTo(1f) + assertThat(transform.scaleY).isEqualTo(1f) + } + with(qsConstraint.getConstraint(R.id.clock)) { + assertThat(transform.scaleX).isGreaterThan(1f) + assertThat(transform.scaleY).isGreaterThan(1f) + } + } + + @Test + fun testEdgeElementsAlignedWithEdgeOrGuide_qs() { + with(qsConstraint) { + assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID) + assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f) + + assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID) + assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f) + + assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd) + .isEqualTo(PARENT_ID) + assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias) + .isEqualTo(1f) + + assertThat(getConstraint(R.id.privacy_container).layout.endToEnd) + .isEqualTo(R.id.end_guide) + assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f) + } + } + + @Test + fun testEdgeElementsAlignedWithEdge_largeScreen() { + with(largeScreenConstraint) { + assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID) + assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f) + + assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID) + assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f) + } + } + + @Test + fun testCarrierAlpha() { + assertThat(qqsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(0f) + assertThat(qsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(1f) + assertThat(largeScreenConstraint.getConstraint(R.id.carrier_group).propertySet.alpha) + .isEqualTo(1f) + } + + @Test + fun testPrivacyChipVisibilityConstraints_notVisible() { + val changes = CombinedShadeHeadersConstraintManagerImpl + .privacyChipVisibilityConstraints(false) + changes() + + with(qqsConstraint) { + assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f) + assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f) + } + + with(qsConstraint) { + assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f) + assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f) + } + + with(largeScreenConstraint) { + assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f) + assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f) + } + } + + @Test + fun testPrivacyChipVisibilityConstraints_visible() { + val changes = CombinedShadeHeadersConstraintManagerImpl + .privacyChipVisibilityConstraints(true) + changes() + + with(qqsConstraint) { + assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(0f) + assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(0f) + } + + with(qsConstraint) { + assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f) + assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f) + } + + with(largeScreenConstraint) { + assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f) + assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f) + } + } + + @Test + fun testEmptyCutoutConstraints() { + val changes = CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints() + changes() + + // QS and Large Screen don't change with cutouts. + assertThat(changes.qsConstraintsChanges).isNull() + assertThat(changes.largeScreenConstraintsChanges).isNull() + + with(qqsConstraint) { + // In this case, the date is constrained on the end by a Barrier determined by either + // privacy or statusIcons + assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.barrier) + assertThat(getConstraint(R.id.statusIcons).layout.startToEnd).isEqualTo(R.id.date) + assertThat(getConstraint(R.id.privacy_container).layout.startToEnd).isEqualTo(R.id.date) + assertThat(getConstraint(R.id.barrier).layout.mReferenceIds).asList().containsExactly( + R.id.statusIcons, + R.id.privacy_container + ) + assertThat(getConstraint(R.id.barrier).layout.mBarrierDirection).isEqualTo(START) + } + } + + @Test + fun testGuidesAreSetInCorrectPosition_largeCutoutSmallerPadding() { + val cutoutStart = 100 + val padding = 10 + val cutoutEnd = 30 + val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints( + cutoutStart, + padding, + cutoutEnd, + padding + ) + changes() + + with(qqsConstraint) { + assertThat(getConstraint(R.id.begin_guide).layout.guideBegin) + .isEqualTo(cutoutStart - padding) + assertThat(getConstraint(R.id.end_guide).layout.guideEnd) + .isEqualTo(cutoutEnd - padding) + } + + with(qsConstraint) { + assertThat(getConstraint(R.id.begin_guide).layout.guideBegin) + .isEqualTo(cutoutStart - padding) + assertThat(getConstraint(R.id.end_guide).layout.guideEnd) + .isEqualTo(cutoutEnd - padding) + } + + assertThat(changes.largeScreenConstraintsChanges).isNull() + } + + @Test + fun testGuidesAreSetInCorrectPosition_smallCutoutLargerPadding() { + val cutoutStart = 5 + val padding = 10 + val cutoutEnd = 10 + + val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints( + cutoutStart, + padding, + cutoutEnd, + padding + ) + changes() + + with(qqsConstraint) { + assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0) + assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0) + } + + with(qsConstraint) { + assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0) + assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0) + } + + assertThat(changes.largeScreenConstraintsChanges).isNull() + } + + @Test + fun testCenterCutoutConstraints_ltr() { + val offsetFromEdge = 400 + val rtl = false + + val changes = CombinedShadeHeadersConstraintManagerImpl + .centerCutoutConstraints(rtl, offsetFromEdge) + changes() + + // In LTR, center_left is towards the start and center_right is towards the end + with(qqsConstraint) { + assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge) + assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge) + assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_left) + assertThat(getConstraint(R.id.statusIcons).layout.startToEnd) + .isEqualTo(R.id.center_right) + assertThat(getConstraint(R.id.privacy_container).layout.startToEnd) + .isEqualTo(R.id.center_right) + } + + with(qsConstraint) { + assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge) + assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge) + + assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left) + assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right) + + assertThat(getConstraint(R.id.statusIcons).layout.startToEnd) + .isNotEqualTo(R.id.center_left) + assertThat(getConstraint(R.id.statusIcons).layout.startToEnd) + .isNotEqualTo(R.id.center_right) + + assertThat(getConstraint(R.id.privacy_container).layout.startToEnd) + .isEqualTo(R.id.center_right) + } + + assertThat(changes.largeScreenConstraintsChanges).isNull() + } + + @Test + fun testCenterCutoutConstraints_rtl() { + val offsetFromEdge = 400 + val rtl = true + + val changes = CombinedShadeHeadersConstraintManagerImpl + .centerCutoutConstraints(rtl, offsetFromEdge) + changes() + + // In RTL, center_left is towards the end and center_right is towards the start + with(qqsConstraint) { + assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge) + assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge) + assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_right) + assertThat(getConstraint(R.id.statusIcons).layout.startToEnd) + .isEqualTo(R.id.center_left) + assertThat(getConstraint(R.id.privacy_container).layout.startToEnd) + .isEqualTo(R.id.center_left) + } + + with(qsConstraint) { + assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge) + assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge) + + assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left) + assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right) + + assertThat(getConstraint(R.id.statusIcons).layout.startToEnd) + .isNotEqualTo(R.id.center_left) + assertThat(getConstraint(R.id.statusIcons).layout.startToEnd) + .isNotEqualTo(R.id.center_right) + + assertThat(getConstraint(R.id.privacy_container).layout.startToEnd) + .isEqualTo(R.id.center_left) + } + + assertThat(changes.largeScreenConstraintsChanges).isNull() + } + + private operator fun ConstraintsChanges.invoke() { + qqsConstraintsChanges?.invoke(qqsConstraint) + qsConstraintsChanges?.invoke(qsConstraint) + largeScreenConstraintsChanges?.invoke(largeScreenConstraint) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt new file mode 100644 index 000000000000..9b2e085560a1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 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.shade + +import android.testing.AndroidTestingRunner +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ConstraintChangeTest : SysuiTestCase() { + + @Test + fun testSumNonNull() { + val mock1: ConstraintChange = mock() + val mock2: ConstraintChange = mock() + + val constraintSet = ConstraintSet() + + val sum = mock1 + mock2 + sum?.invoke(constraintSet) + + val inOrder = inOrder(mock1, mock2) + inOrder.verify(mock1).invoke(constraintSet) + inOrder.verify(mock2).invoke(constraintSet) + } + + @Test + fun testSumThisNull() { + val mock: ConstraintChange = mock() + val constraintSet = ConstraintSet() + + val sum = (null as? ConstraintChange?) + mock + sum?.invoke(constraintSet) + + verify(mock).invoke(constraintSet) + } + + @Test + fun testSumThisNull_notWrapped() { + val change: ConstraintChange = {} + + val sum = (null as? ConstraintChange?) + change + assertThat(sum).isSameInstanceAs(change) + } + + @Test + fun testSumOtherNull() { + val mock: ConstraintChange = mock() + val constraintSet = ConstraintSet() + + val sum = mock + (null as? ConstraintChange?) + sum?.invoke(constraintSet) + + verify(mock).invoke(constraintSet) + } + + @Test + fun testSumOtherNull_notWrapped() { + val change: ConstraintChange = {} + + val sum = change + (null as? ConstraintChange?) + assertThat(sum).isSameInstanceAs(change) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt new file mode 100644 index 000000000000..0abb08427478 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022 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.shade + +import android.testing.AndroidTestingRunner +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.inOrder + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ConstraintChangesTest : SysuiTestCase() { + + @Test + fun testSumWithoutNulls() { + val mockQQS1: ConstraintChange = mock() + val mockQS1: ConstraintChange = mock() + val mockLS1: ConstraintChange = mock() + val mockQQS2: ConstraintChange = mock() + val mockQS2: ConstraintChange = mock() + val mockLS2: ConstraintChange = mock() + + val changes1 = ConstraintsChanges(mockQQS1, mockQS1, mockLS1) + val changes2 = ConstraintsChanges(mockQQS2, mockQS2, mockLS2) + + val sum = changes1 + changes2 + + val constraintSet = ConstraintSet() + sum.qqsConstraintsChanges?.invoke(constraintSet) + sum.qsConstraintsChanges?.invoke(constraintSet) + sum.largeScreenConstraintsChanges?.invoke(constraintSet) + + val inOrder = inOrder(mockQQS1, mockQS1, mockLS1, mockQQS2, mockQS2, mockLS2) + + inOrder.verify(mockQQS1).invoke(constraintSet) + inOrder.verify(mockQQS2).invoke(constraintSet) + inOrder.verify(mockQS1).invoke(constraintSet) + inOrder.verify(mockQS2).invoke(constraintSet) + inOrder.verify(mockLS1).invoke(constraintSet) + inOrder.verify(mockLS2).invoke(constraintSet) + } + + @Test + fun testSumWithSomeNulls() { + val mockQQS: ConstraintChange = mock() + val mockQS: ConstraintChange = mock() + + val changes1 = ConstraintsChanges(mockQQS, null, null) + val changes2 = ConstraintsChanges(null, mockQS, null) + + val sum = changes1 + changes2 + + assertThat(sum.qqsConstraintsChanges).isSameInstanceAs(mockQQS) + assertThat(sum.qsConstraintsChanges).isSameInstanceAs(mockQS) + assertThat(sum.largeScreenConstraintsChanges).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt new file mode 100644 index 000000000000..ed1a13b36d6c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -0,0 +1,659 @@ +/* + * Copyright (C) 2022 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.shade + +import android.content.Context +import android.content.res.Resources +import android.content.res.XmlResourceParser +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.view.DisplayCutout +import android.view.View +import android.view.WindowInsets +import android.widget.TextView +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ShadeInterpolation +import com.android.systemui.battery.BatteryMeterView +import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.qs.ChipVisibilityListener +import com.android.systemui.qs.HeaderPrivacyIconsController +import com.android.systemui.qs.carrier.QSCarrierGroup +import com.android.systemui.qs.carrier.QSCarrierGroupController +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT +import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider +import com.android.systemui.statusbar.phone.StatusBarIconController +import com.android.systemui.statusbar.phone.StatusIconContainer +import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.VariableDateView +import com.android.systemui.statusbar.policy.VariableDateViewController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit + +private val EMPTY_CHANGES = ConstraintsChanges() + +/** + * Tests for [LargeScreenShadeHeaderController] when [Flags.COMBINED_QS_HEADERS] is `true`. + * + * Once that flag is removed, this class will be combined with + * [LargeScreenShadeHeaderControllerTest]. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { + + @Mock + private lateinit var statusIcons: StatusIconContainer + @Mock + private lateinit var statusBarIconController: StatusBarIconController + @Mock + private lateinit var qsCarrierGroupController: QSCarrierGroupController + @Mock + private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder + @Mock + private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var clock: TextView + @Mock + private lateinit var date: VariableDateView + @Mock + private lateinit var carrierGroup: QSCarrierGroup + @Mock + private lateinit var batteryMeterView: BatteryMeterView + @Mock + private lateinit var batteryMeterViewController: BatteryMeterViewController + @Mock + private lateinit var privacyIconsController: HeaderPrivacyIconsController + @Mock + private lateinit var insetsProvider: StatusBarContentInsetsProvider + @Mock + private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory + @Mock + private lateinit var variableDateViewController: VariableDateViewController + @Mock + private lateinit var dumpManager: DumpManager + @Mock + private lateinit var combinedShadeHeadersConstraintManager: + CombinedShadeHeadersConstraintManager + + @Mock + private lateinit var mockedContext: Context + @Mock(answer = Answers.RETURNS_MOCKS) + private lateinit var view: MotionLayout + + @Mock + private lateinit var qqsConstraints: ConstraintSet + @Mock + private lateinit var qsConstraints: ConstraintSet + @Mock + private lateinit var largeScreenConstraints: ConstraintSet + + @JvmField @Rule + val mockitoRule = MockitoJUnit.rule() + var viewVisibility = View.GONE + + private lateinit var controller: LargeScreenShadeHeaderController + private lateinit var carrierIconSlots: List<String> + private val configurationController = FakeConfigurationController() + + @Before + fun setUp() { + whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock) + whenever(clock.context).thenReturn(mockedContext) + + whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date) + whenever(date.context).thenReturn(mockedContext) + whenever(variableDateViewControllerFactory.create(any())) + .thenReturn(variableDateViewController) + + whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup) + whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon)) + .thenReturn(batteryMeterView) + + whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons) + whenever(statusIcons.context).thenReturn(context) + + whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any())) + .thenReturn(qsCarrierGroupControllerBuilder) + whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController) + + whenever(view.context).thenReturn(context) + whenever(view.resources).thenReturn(context.resources) + whenever(view.setVisibility(ArgumentMatchers.anyInt())).then { + viewVisibility = it.arguments[0] as Int + null + } + whenever(view.visibility).thenAnswer { _ -> viewVisibility } + + whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true) + whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true) + + setUpDefaultInsets() + setUpMotionLayout(view) + + controller = LargeScreenShadeHeaderController( + view, + statusBarIconController, + privacyIconsController, + insetsProvider, + configurationController, + variableDateViewControllerFactory, + batteryMeterViewController, + dumpManager, + featureFlags, + qsCarrierGroupControllerBuilder, + combinedShadeHeadersConstraintManager + ) + whenever(view.isAttachedToWindow).thenReturn(true) + controller.init() + carrierIconSlots = listOf( + context.getString(com.android.internal.R.string.status_bar_mobile)) + } + + @Test + fun testCorrectConstraints() { + val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java) + + verify(qqsConstraints).load(eq(context), capture(captor)) + assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header) + + verify(qsConstraints).load(eq(context), capture(captor)) + assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new) + + verify(largeScreenConstraints).load(eq(context), capture(captor)) + assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header) + } + + @Test + fun testControllersCreatedAndInitialized() { + verify(variableDateViewController).init() + + verify(batteryMeterViewController).init() + verify(batteryMeterViewController).ignoreTunerUpdates() + verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) + + val inOrder = inOrder(qsCarrierGroupControllerBuilder) + inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup) + inOrder.verify(qsCarrierGroupControllerBuilder).build() + } + + @Test + fun testClockPivotLtr() { + val width = 200 + whenever(clock.width).thenReturn(width) + whenever(clock.isLayoutRtl).thenReturn(false) + + val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) + verify(clock).addOnLayoutChangeListener(capture(captor)) + + captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7) + verify(clock).pivotX = 0f + } + + @Test + fun testClockPivotRtl() { + val width = 200 + whenever(clock.width).thenReturn(width) + whenever(clock.isLayoutRtl).thenReturn(true) + + val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) + verify(clock).addOnLayoutChangeListener(capture(captor)) + + captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7) + verify(clock).pivotX = width.toFloat() + } + + @Test + fun testShadeExpanded_true() { + // When shade is expanded, view should be visible regardless of largeScreenActive + controller.largeScreenActive = false + controller.qsVisible = true + assertThat(viewVisibility).isEqualTo(View.VISIBLE) + + controller.largeScreenActive = true + assertThat(viewVisibility).isEqualTo(View.VISIBLE) + } + + @Test + fun testShadeExpanded_false() { + // When shade is not expanded, view should be invisible regardless of largeScreenActive + controller.largeScreenActive = false + controller.qsVisible = false + assertThat(viewVisibility).isEqualTo(View.INVISIBLE) + + controller.largeScreenActive = true + assertThat(viewVisibility).isEqualTo(View.INVISIBLE) + } + + @Test + fun testLargeScreenActive_true() { + controller.largeScreenActive = false // Make sure there's a change + clearInvocations(view) + + controller.largeScreenActive = true + + verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID) + } + + @Test + fun testLargeScreenActive_false() { + controller.largeScreenActive = true // Make sure there's a change + clearInvocations(view) + + controller.largeScreenActive = false + + verify(view).setTransition(HEADER_TRANSITION_ID) + } + + @Test + fun testShadeExpandedFraction() { + // View needs to be visible for this to actually take effect + controller.qsVisible = true + + clearInvocations(view) + controller.shadeExpandedFraction = 0.3f + verify(view).alpha = ShadeInterpolation.getContentAlpha(0.3f) + + clearInvocations(view) + controller.shadeExpandedFraction = 1f + verify(view).alpha = ShadeInterpolation.getContentAlpha(1f) + + clearInvocations(view) + controller.shadeExpandedFraction = 0f + verify(view).alpha = ShadeInterpolation.getContentAlpha(0f) + } + + @Test + fun testQsExpandedFraction_headerTransition() { + controller.qsVisible = true + controller.largeScreenActive = false + + clearInvocations(view) + controller.qsExpandedFraction = 0.3f + verify(view).progress = 0.3f + } + + @Test + fun testQsExpandedFraction_largeScreen() { + controller.qsVisible = true + controller.largeScreenActive = true + + clearInvocations(view) + controller.qsExpandedFraction = 0.3f + verify(view, never()).progress = anyFloat() + } + + @Test + fun testScrollY_headerTransition() { + controller.largeScreenActive = false + + clearInvocations(view) + controller.qsScrollY = 20 + verify(view).scrollY = 20 + } + + @Test + fun testScrollY_largeScreen() { + controller.largeScreenActive = true + + clearInvocations(view) + controller.qsScrollY = 20 + verify(view, never()).scrollY = anyInt() + } + + @Test + fun testPrivacyChipVisibilityChanged_visible_changesCorrectConstraints() { + val chipVisibleChanges = createMockConstraintChanges() + val chipNotVisibleChanges = createMockConstraintChanges() + + whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true)) + .thenReturn(chipVisibleChanges) + whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false)) + .thenReturn(chipNotVisibleChanges) + + val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java) + verify(privacyIconsController).chipVisibilityListener = capture(captor) + + captor.value.onChipVisibilityRefreshed(true) + + verify(chipVisibleChanges.qqsConstraintsChanges)!!.invoke(qqsConstraints) + verify(chipVisibleChanges.qsConstraintsChanges)!!.invoke(qsConstraints) + verify(chipVisibleChanges.largeScreenConstraintsChanges)!!.invoke(largeScreenConstraints) + + verify(chipNotVisibleChanges.qqsConstraintsChanges, never())!!.invoke(any()) + verify(chipNotVisibleChanges.qsConstraintsChanges, never())!!.invoke(any()) + verify(chipNotVisibleChanges.largeScreenConstraintsChanges, never())!!.invoke(any()) + } + + @Test + fun testPrivacyChipVisibilityChanged_notVisible_changesCorrectConstraints() { + val chipVisibleChanges = createMockConstraintChanges() + val chipNotVisibleChanges = createMockConstraintChanges() + + whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true)) + .thenReturn(chipVisibleChanges) + whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false)) + .thenReturn(chipNotVisibleChanges) + + val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java) + verify(privacyIconsController).chipVisibilityListener = capture(captor) + + captor.value.onChipVisibilityRefreshed(false) + + verify(chipVisibleChanges.qqsConstraintsChanges, never())!!.invoke(qqsConstraints) + verify(chipVisibleChanges.qsConstraintsChanges, never())!!.invoke(qsConstraints) + verify(chipVisibleChanges.largeScreenConstraintsChanges, never())!! + .invoke(largeScreenConstraints) + + verify(chipNotVisibleChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(chipNotVisibleChanges.qsConstraintsChanges)!!.invoke(any()) + verify(chipNotVisibleChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testInsetsGuides_ltr() { + whenever(view.isLayoutRtl).thenReturn(false) + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + val (insetLeft, insetRight) = 30 to 40 + val (paddingStart, paddingEnd) = 10 to 20 + whenever(view.paddingStart).thenReturn(paddingStart) + whenever(view.paddingEnd).thenReturn(paddingEnd) + + mockInsetsProvider(insetLeft to insetRight, false) + + whenever(combinedShadeHeadersConstraintManager + .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt()) + ).thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets()) + + verify(combinedShadeHeadersConstraintManager) + .edgesGuidelinesConstraints(insetLeft, paddingStart, insetRight, paddingEnd) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testInsetsGuides_rtl() { + whenever(view.isLayoutRtl).thenReturn(true) + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + val (insetLeft, insetRight) = 30 to 40 + val (paddingStart, paddingEnd) = 10 to 20 + whenever(view.paddingStart).thenReturn(paddingStart) + whenever(view.paddingEnd).thenReturn(paddingEnd) + + mockInsetsProvider(insetLeft to insetRight, false) + + whenever(combinedShadeHeadersConstraintManager + .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt()) + ).thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets()) + + verify(combinedShadeHeadersConstraintManager) + .edgesGuidelinesConstraints(insetRight, paddingStart, insetLeft, paddingEnd) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testNullCutout() { + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints()) + .thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets(null)) + + verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints() + verify(combinedShadeHeadersConstraintManager, never()) + .centerCutoutConstraints(anyBoolean(), anyInt()) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testEmptyCutout() { + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints()) + .thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets()) + + verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints() + verify(combinedShadeHeadersConstraintManager, never()) + .centerCutoutConstraints(anyBoolean(), anyInt()) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testCornerCutout_emptyRect() { + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + mockInsetsProvider(0 to 0, true) + + whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints()) + .thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets()) + + verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints() + verify(combinedShadeHeadersConstraintManager, never()) + .centerCutoutConstraints(anyBoolean(), anyInt()) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testCornerCutout_nonEmptyRect() { + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + mockInsetsProvider(0 to 0, true) + + whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints()) + .thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(1, 2, 3, 4))) + + verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints() + verify(combinedShadeHeadersConstraintManager, never()) + .centerCutoutConstraints(anyBoolean(), anyInt()) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testTopCutout_ltr() { + val width = 100 + val paddingLeft = 10 + val paddingRight = 20 + val cutoutWidth = 30 + + whenever(view.isLayoutRtl).thenReturn(false) + whenever(view.width).thenReturn(width) + whenever(view.paddingLeft).thenReturn(paddingLeft) + whenever(view.paddingRight).thenReturn(paddingRight) + + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + mockInsetsProvider(0 to 0, false) + + whenever(combinedShadeHeadersConstraintManager + .centerCutoutConstraints(anyBoolean(), anyInt()) + ).thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1))) + + verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints() + val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2 + verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(false, offset) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + @Test + fun testTopCutout_rtl() { + val width = 100 + val paddingLeft = 10 + val paddingRight = 20 + val cutoutWidth = 30 + + whenever(view.isLayoutRtl).thenReturn(true) + whenever(view.width).thenReturn(width) + whenever(view.paddingLeft).thenReturn(paddingLeft) + whenever(view.paddingRight).thenReturn(paddingRight) + + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + val mockConstraintsChanges = createMockConstraintChanges() + + mockInsetsProvider(0 to 0, false) + + whenever(combinedShadeHeadersConstraintManager + .centerCutoutConstraints(anyBoolean(), anyInt()) + ).thenReturn(mockConstraintsChanges) + + captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1))) + + verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints() + val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2 + verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(true, offset) + + verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any()) + verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any()) + } + + private fun createWindowInsets( + topCutout: Rect? = Rect() + ): WindowInsets { + val windowInsets: WindowInsets = mock() + val displayCutout: DisplayCutout = mock() + whenever(windowInsets.displayCutout) + .thenReturn(if (topCutout != null) displayCutout else null) + whenever(displayCutout.boundingRectTop).thenReturn(topCutout) + + return windowInsets + } + + private fun mockInsetsProvider( + insets: Pair<Int, Int> = 0 to 0, + cornerCutout: Boolean = false, + ) { + whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(insets.toAndroidPair()) + whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(cornerCutout) + } + + private fun createMockConstraintChanges(): ConstraintsChanges { + return ConstraintsChanges(mock(), mock(), mock()) + } + + private fun XmlResourceParser.getResId(): Int { + return Resources.getAttributeSetSourceResId(this) + } + + private fun setUpMotionLayout(motionLayout: MotionLayout) { + whenever(motionLayout.getConstraintSet(QQS_HEADER_CONSTRAINT)).thenReturn(qqsConstraints) + whenever(motionLayout.getConstraintSet(QS_HEADER_CONSTRAINT)).thenReturn(qsConstraints) + whenever(motionLayout.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)) + .thenReturn(largeScreenConstraints) + } + + private fun setUpDefaultInsets() { + whenever(combinedShadeHeadersConstraintManager + .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt()) + ).thenReturn(EMPTY_CHANGES) + whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints()) + .thenReturn(EMPTY_CHANGES) + whenever(combinedShadeHeadersConstraintManager + .centerCutoutConstraints(anyBoolean(), anyInt()) + ).thenReturn(EMPTY_CHANGES) + whenever(combinedShadeHeadersConstraintManager + .privacyChipVisibilityConstraints(anyBoolean()) + ).thenReturn(EMPTY_CHANGES) + whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(Pair(0, 0).toAndroidPair()) + whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false) + } + + private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> { + return android.util.Pair(first, second) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt index 80664013f95d..02b26dbbc32d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -1,10 +1,8 @@ -package com.android.systemui.statusbar.phone +package com.android.systemui.shade import android.app.StatusBarManager import android.content.Context -import android.content.res.TypedArray import android.testing.AndroidTestingRunner -import android.util.TypedValue.COMPLEX_UNIT_PX import android.view.View import android.widget.TextView import androidx.test.filters.SmallTest @@ -19,19 +17,24 @@ import com.android.systemui.flags.Flags import com.android.systemui.qs.HeaderPrivacyIconsController import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider +import com.android.systemui.statusbar.phone.StatusBarIconController +import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.VariableDateViewController +import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit @SmallTest @RunWith(AndroidTestingRunner::class) @@ -49,10 +52,14 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var batteryMeterView: BatteryMeterView @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController @Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController + @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider + @Mock private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory + @Mock private lateinit var variableDateViewController: VariableDateViewController @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var combinedShadeHeadersConstraintManager: + CombinedShadeHeadersConstraintManager @Mock private lateinit var mockedContext: Context - @Mock private lateinit var typedArray: TypedArray @JvmField @Rule val mockitoRule = MockitoJUnit.rule() var viewVisibility = View.GONE @@ -65,7 +72,6 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { fun setup() { whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock) whenever(clock.context).thenReturn(mockedContext) - whenever(mockedContext.obtainStyledAttributes(anyInt(), any())).thenReturn(typedArray) whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date) whenever(date.context).thenReturn(mockedContext) whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup) @@ -73,6 +79,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { .thenReturn(batteryMeterView) whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons) whenever(view.context).thenReturn(context) + whenever(view.resources).thenReturn(context.resources) whenever(statusIcons.context).thenReturn(context) whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any())) .thenReturn(qsCarrierGroupControllerBuilder) @@ -82,27 +89,39 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { null } whenever(view.visibility).thenAnswer { _ -> viewVisibility } + whenever(variableDateViewControllerFactory.create(any())) + .thenReturn(variableDateViewController) whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false) mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController( view, statusBarIconController, privacyIconsController, + insetsProvider, configurationController, - qsCarrierGroupControllerBuilder, - featureFlags, + variableDateViewControllerFactory, batteryMeterViewController, - dumpManager + dumpManager, + featureFlags, + qsCarrierGroupControllerBuilder, + combinedShadeHeadersConstraintManager ) + whenever(view.isAttachedToWindow).thenReturn(true) + mLargeScreenShadeHeaderController.init() carrierIconSlots = listOf( context.getString(com.android.internal.R.string.status_bar_mobile)) } + @After + fun verifyEveryTest() { + verifyZeroInteractions(combinedShadeHeadersConstraintManager) + } + @Test fun setVisible_onlyWhenActive() { makeShadeVisible() assertThat(viewVisibility).isEqualTo(View.VISIBLE) - mLargeScreenShadeHeaderController.active = false + mLargeScreenShadeHeaderController.largeScreenActive = false assertThat(viewVisibility).isEqualTo(View.GONE) } @@ -156,41 +175,16 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { } private fun makeShadeVisible() { - mLargeScreenShadeHeaderController.active = true - mLargeScreenShadeHeaderController.shadeExpanded = true + mLargeScreenShadeHeaderController.largeScreenActive = true + mLargeScreenShadeHeaderController.qsVisible = true } @Test - fun updateConfig_changesFontSize() { - val updatedTextPixelSize = 32 - setReturnTextSize(updatedTextPixelSize) - + fun updateConfig_changesFontStyle() { configurationController.notifyDensityOrFontScaleChanged() - verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat()) - verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat()) - verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status) - } - - @Test - fun updateConfig_changesFontSizeMultipleTimes() { - val updatedTextPixelSize1 = 32 - setReturnTextSize(updatedTextPixelSize1) - configurationController.notifyDensityOrFontScaleChanged() - verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat()) - verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat()) - verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status) - clearInvocations(carrierGroup) - - val updatedTextPixelSize2 = 42 - setReturnTextSize(updatedTextPixelSize2) - configurationController.notifyDensityOrFontScaleChanged() - verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat()) - verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat()) - verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status) - } - - private fun setReturnTextSize(resultTextSize: Int) { - whenever(typedArray.getDimensionPixelSize(anyInt(), anyInt())).thenReturn(resultTextSize) + verify(clock).setTextAppearance(R.style.TextAppearance_QS_Status) + verify(date).setTextAppearance(R.style.TextAppearance_QS_Status) + verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 5abcff3c56f6..e2673bb74084 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -143,10 +143,8 @@ import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; -import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.PanelViewController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -1182,11 +1180,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mStatusBarStateController.setState(SHADE); when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true); mNotificationPanelViewController.updateResources(); - verify(mLargeScreenShadeHeaderController).setActive(true); + verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true); when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false); mNotificationPanelViewController.updateResources(); - verify(mLargeScreenShadeHeaderController).setActive(false); + verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false); } @Test @@ -1286,6 +1284,29 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void testPanelClosedWhenClosingQsInSplitShade() { + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, + /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0); + enableSplitShade(/* enabled= */ true); + mNotificationPanelViewController.setExpandedFraction(1f); + + assertThat(mNotificationPanelViewController.isClosing()).isFalse(); + mNotificationPanelViewController.animateCloseQs(false); + assertThat(mNotificationPanelViewController.isClosing()).isTrue(); + } + + @Test + public void testPanelStaysOpenWhenClosingQs() { + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, + /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0); + mNotificationPanelViewController.setExpandedFraction(1f); + + assertThat(mNotificationPanelViewController.isClosing()).isFalse(); + mNotificationPanelViewController.animateCloseQs(false); + assertThat(mNotificationPanelViewController.isClosing()).isFalse(); + } + + @Test public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { mNotificationPanelViewController.mQs = mQs; when(mQsFrame.getX()).thenReturn(0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 72d3c2e95d75..5386171eeda2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -27,6 +27,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; +import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; import static com.google.common.truth.Truth.assertThat; @@ -61,6 +62,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; @@ -87,6 +89,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Mock StatusBarStateController mStatusBarStateController; @Mock + KeyguardStateController mKeyguardStateController; + @Mock HeadsUpManager mHeadsUpManager; @Mock NotificationInterruptLogger mLogger; @@ -106,6 +110,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false); mNotifInterruptionStateProvider = new NotificationInterruptStateProviderImpl( @@ -116,6 +121,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { mNotificationFilter, mBatteryController, mStatusBarStateController, + mKeyguardStateController, mHeadsUpManager, mLogger, mMockHandler, @@ -427,6 +433,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + testShouldNotFullScreen_notPendingIntent(); + } + + @Test public void testShouldNotFullScreen_notPendingIntent() throws RemoteException { NotificationEntry entry = createNotification(IMPORTANCE_HIGH); when(mPowerManager.isInteractive()).thenReturn(true); @@ -441,6 +453,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldNotFullScreen_notHighImportance_withStrictFlag() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + testShouldNotFullScreen_notHighImportance(); + } + + @Test public void testShouldNotFullScreen_notHighImportance() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); @@ -455,6 +473,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldNotFullScreen_isGroupAlertSilenced_withStrictFlag() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + testShouldNotFullScreen_isGroupAlertSilenced(); + } + + @Test public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true); when(mPowerManager.isInteractive()).thenReturn(false); @@ -469,6 +493,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldFullScreen_notInteractive_withStrictFlag() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + testShouldFullScreen_notInteractive(); + } + + @Test public void testShouldFullScreen_notInteractive() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(false); @@ -483,6 +513,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldFullScreen_isDreaming_withStrictFlag() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + testShouldFullScreen_isDreaming(); + } + + @Test public void testShouldFullScreen_isDreaming() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); @@ -497,6 +533,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldFullScreen_onKeyguard_withStrictFlag() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + testShouldFullScreen_onKeyguard(); + } + + @Test public void testShouldFullScreen_onKeyguard() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); @@ -511,6 +553,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldNotFullScreen_willHun_withStrictFlag() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + testShouldNotFullScreen_willHun(); + } + + @Test public void testShouldNotFullScreen_willHun() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); @@ -542,6 +590,66 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { verify(mLogger).logFullscreen(entry, "Expected not to HUN"); } + @Test + public void testShouldFullScreen_snoozed_occluding_withStrictRules() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(true); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + verify(mLogger).logNoHeadsUpPackageSnoozed(entry); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded"); + } + + @Test + public void testShouldFullScreen_snoozed_lockedShade_withStrictRules() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED); + when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + verify(mLogger).logNoHeadsUpPackageSnoozed(entry); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded"); + } + + @Test + public void testShouldNotFullScreen_snoozed_unlocked_withStrictRules() throws Exception { + when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(false); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + verify(mLogger).logNoHeadsUpPackageSnoozed(entry); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard"); + verify(mLogger, never()).logFullscreen(any(), any()); + } + /** * Bubbles can happen. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt index 64d025628754..214ba16dfc44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt @@ -98,7 +98,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { @Test fun testPrepareDialogForApp_onlyDefaultChannel() { - group.addChannel(channelDefault) + group.channels = listOf(channelDefault) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, setOf(channelDefault), appIcon, clickListener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index b75c52ad283e..c6fb0ce34e85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -306,8 +306,13 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mNotificationInterruptStateProvider = new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), mPowerManager, - mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter, - mStatusBarStateController, mBatteryController, mHeadsUpManager, + mDreamManager, + mAmbientDisplayConfiguration, + mNotificationFilter, + mStatusBarStateController, + mKeyguardStateController, + mBatteryController, + mHeadsUpManager, mock(NotificationInterruptLogger.class), new Handler(TestableLooper.get(this).getLooper()), mock(NotifPipelineFlags.class), @@ -1036,15 +1041,28 @@ public class CentralSurfacesImplTest extends SysuiTestCase { AmbientDisplayConfiguration ambientDisplayConfiguration, NotificationFilter filter, StatusBarStateController controller, + KeyguardStateController keyguardStateController, BatteryController batteryController, HeadsUpManager headsUpManager, NotificationInterruptLogger logger, Handler mainHandler, NotifPipelineFlags flags, KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) { - super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter, - batteryController, controller, headsUpManager, logger, mainHandler, - flags, keyguardNotificationVisibilityProvider); + super( + contentResolver, + powerManager, + dreamManager, + ambientDisplayConfiguration, + filter, + batteryController, + controller, + keyguardStateController, + headsUpManager, + logger, + mainHandler, + flags, + keyguardNotificationVisibilityProvider + ); mUseHeadsUp = true; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index ba29e953c73d..9892448aed03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.shade.PanelViewController import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.unfold.SysUIUnfoldComponent diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index 8d686ae94e79..d6c995bef229 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -20,6 +20,7 @@ import android.view.MotionEvent import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.shade.PanelViewController import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 4b5d1f747ca0..fdb29770cbb7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -16,12 +16,14 @@ package com.android.systemui.statusbar.phone; import static android.view.Display.DEFAULT_DISPLAY; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.PendingIntent; import android.app.StatusBarManager; import android.metrics.LogMaker; import android.support.test.metricshelper.MetricsAsserts; @@ -80,6 +82,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { private FakeMetricsLogger mMetricsLogger; private ShadeController mShadeController = mock(ShadeController.class); private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class); + private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class); + private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class); private InitController mInitController = new InitController(); @Before @@ -114,7 +118,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(ScrimController.class), mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), - mock(KeyguardStateController.class), + mKeyguardStateController, mock(KeyguardIndicationController.class), mCentralSurfaces, mock(ShadeControllerImpl.class), @@ -130,7 +134,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mInitController, mNotificationInterruptStateProvider, mock(NotificationRemoteInputManager.class), - mock(NotifPipelineFlags.class), + mNotifPipelineFlags, mock(NotificationRemoteInputManager.Callback.class), mock(NotificationListContainer.class)); mInitController.executePostInitTasks(); @@ -141,6 +145,19 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + public void testNoSuppressHeadsUp_default() { + Notification n = new Notification.Builder(getContext(), "a").build(); + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(n) + .build(); + + assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry)); + } + + @Test public void testSuppressHeadsUp_disabledStatusBar() { Notification n = new Notification.Builder(getContext(), "a").build(); NotificationEntry entry = new NotificationEntryBuilder() @@ -176,6 +193,63 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + public void testNoSuppressHeadsUp_FSI_occludedKeygaurd() { + when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false); + Notification n = new Notification.Builder(getContext(), "a") + .setFullScreenIntent(mock(PendingIntent.class), true) + .build(); + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(n) + .build(); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(true); + when(mCentralSurfaces.isOccluded()).thenReturn(true); + assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry)); + } + + @Test + public void testSuppressHeadsUp_FSI_nonOccludedKeygaurd() { + when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false); + Notification n = new Notification.Builder(getContext(), "a") + .setFullScreenIntent(mock(PendingIntent.class), true) + .build(); + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(n) + .build(); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + when(mCentralSurfaces.isOccluded()).thenReturn(false); + assertTrue(mInterruptSuppressor.suppressAwakeHeadsUp(entry)); + } + + @Test + public void testNoSuppressHeadsUp_FSI_nonOccludedKeygaurd_withNewFlag() { + when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); + Notification n = new Notification.Builder(getContext(), "a") + .setFullScreenIntent(mock(PendingIntent.class), true) + .build(); + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(n) + .build(); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + when(mCentralSurfaces.isOccluded()).thenReturn(false); + assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry)); + } + + @Test public void testSuppressInterruptions_vrMode() { Notification n = new Notification.Builder(getContext(), "a").build(); NotificationEntry entry = new NotificationEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 6a0124a3be8a..9866013093cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -321,6 +321,7 @@ public class BubblesTest extends SysuiTestCase { mock(AmbientDisplayConfiguration.class), mock(NotificationFilter.class), mock(StatusBarStateController.class), + mock(KeyguardStateController.class), mock(BatteryController.class), mock(HeadsUpManager.class), mock(NotificationInterruptLogger.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java index a7f0dc22e849..d80ea154e77a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; public class TestableNotificationInterruptStateProviderImpl extends NotificationInterruptStateProviderImpl { @@ -41,6 +42,7 @@ public class TestableNotificationInterruptStateProviderImpl AmbientDisplayConfiguration ambientDisplayConfiguration, NotificationFilter filter, StatusBarStateController statusBarStateController, + KeyguardStateController keyguardStateController, BatteryController batteryController, HeadsUpManager headsUpManager, NotificationInterruptLogger logger, @@ -54,6 +56,7 @@ public class TestableNotificationInterruptStateProviderImpl filter, batteryController, statusBarStateController, + keyguardStateController, headsUpManager, logger, mainHandler, diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 72ade2632078..9c2136675dfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -33,7 +33,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; -import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; @@ -73,7 +72,6 @@ public class WMShellTest extends SysuiTestCase { @Mock OneHanded mOneHanded; @Mock WakefulnessLifecycle mWakefulnessLifecycle; @Mock ProtoTracer mProtoTracer; - @Mock ShellCommandHandler mShellCommandHandler; @Mock UserInfoController mUserInfoController; @Mock ShellExecutor mSysUiMainExecutor; @@ -82,10 +80,10 @@ public class WMShellTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip), - Optional.of(mSplitScreen), Optional.of(mOneHanded), - Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController, - mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, - mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor); + Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue, + mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor, + mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, + mUserInfoController, mSysUiMainExecutor); } @Test diff --git a/services/contentcapture/Android.bp b/services/contentcapture/Android.bp index 5392c2cde3b8..434f239dbddc 100644 --- a/services/contentcapture/Android.bp +++ b/services/contentcapture/Android.bp @@ -17,9 +17,6 @@ filegroup { java_library_static { name: "services.contentcapture", defaults: ["platform_service_defaults"], - srcs: [ - ":services.contentcapture-sources", - "java/**/*.logtags", - ], + srcs: [":services.contentcapture-sources"], libs: ["services.core"], } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 0428b2322f27..41a759254909 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -60,7 +60,6 @@ import android.service.contentcapture.SnapshotData; import android.service.voice.VoiceInteractionManagerInternal; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -89,11 +88,6 @@ final class ContentCapturePerUserService private static final String TAG = ContentCapturePerUserService.class.getSimpleName(); - private static final int EVENT_LOG_CONNECT_STATE_DIED = 0; - static final int EVENT_LOG_CONNECT_STATE_CONNECTED = 1; - static final int EVENT_LOG_CONNECT_STATE_DISCONNECTED = 2; - - @GuardedBy("mLock") private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>(); @@ -196,13 +190,9 @@ final class ContentCapturePerUserService Slog.w(TAG, "remote service died: " + service); synchronized (mLock) { mZombie = true; - ComponentName serviceComponent = getServiceComponentName(); writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED, - serviceComponent); - EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId, - serviceComponent != null ? serviceComponent.flattenToShortString() : "", - EVENT_LOG_CONNECT_STATE_DIED); + getServiceComponentName()); } } @@ -624,16 +614,11 @@ final class ContentCapturePerUserService ? "null_activities" : activities.size() + " activities") + ")" + " for user " + mUserId); } - int packageCount = packages != null ? packages.size() : 0; - int activityCount = activities != null ? activities.size() : 0; ArraySet<String> oldList = mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId); - EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, oldList.size()); mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); - EventLog.writeEvent(EventLogTags.CC_SET_ALLOWLIST, mUserId, - packageCount, activityCount); writeSetWhitelistEvent(getServiceComponentName(), packages, activities); updateContentCaptureOptions(oldList); @@ -714,14 +699,12 @@ final class ContentCapturePerUserService private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) { ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions .getWhitelistedPackages(mUserId); - EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, adding.size()); if (oldList != null && adding != null) { adding.removeAll(oldList); } int N = adding != null ? adding.size() : 0; - EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, N); for (int i = 0; i < N; i++) { String packageName = adding.valueAt(i); ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions diff --git a/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags deleted file mode 100644 index 6722b9ed3c5f..000000000000 --- a/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags +++ /dev/null @@ -1,8 +0,0 @@ -# See system/logging/logcat/event.logtags for a description of the format of this file. - -option java_package com.android.server.contentcapture - -53200 cc_connect_state_changed (User|1|5),(component|3),(type|1) -53201 cc_set_allowlist (User|1|5),(package_count|1),(activity_count|1) -53202 cc_current_allowlist (User|1|5),(count|1) -53203 cc_update_options (User|1|5),(count|1) diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index e22a9d07b4ae..1efe55aa0767 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -31,7 +31,6 @@ import android.service.contentcapture.IContentCaptureService; import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.IDataShareCallback; import android.service.contentcapture.SnapshotData; -import android.util.EventLog; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.DataRemovalRequest; @@ -48,7 +47,6 @@ final class RemoteContentCaptureService private final IBinder mServerCallback; private final int mIdleUnbindTimeoutMs; private final ContentCapturePerUserService mPerUserService; - private final int mUserId; RemoteContentCaptureService(Context context, String serviceInterface, ComponentName serviceComponentName, IContentCaptureServiceCallback callback, int userId, @@ -63,7 +61,6 @@ final class RemoteContentCaptureService mPerUserService = perUserService; mServerCallback = callback.asBinder(); mIdleUnbindTimeoutMs = idleUnbindTimeoutMs; - mUserId = userId; // Bind right away, which will trigger a onConnected() on service's ensureBoundLocked(); @@ -91,9 +88,6 @@ final class RemoteContentCaptureService writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED, mComponentName); - EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId, - mComponentName != null ? mComponentName.flattenToShortString() : "", - ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_CONNECTED); } finally { // Update the system-service state, in case the service reconnected after // dying @@ -104,9 +98,6 @@ final class RemoteContentCaptureService writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED, mComponentName); - EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId, - mComponentName != null ? mComponentName.flattenToShortString() : "", - ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_DISCONNECTED); } } catch (Exception e) { Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java index d99abcd4b3d2..9494547c7aaf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java @@ -70,20 +70,24 @@ public class UsageStats { private int mAcceptCount; private int mRejectCount; - private SparseIntArray mErrorCount; + private int mErrorCount; + private int mAuthAttemptCount; + private SparseIntArray mErrorFrequencyMap; private long mAcceptLatency; private long mRejectLatency; - private SparseLongArray mErrorLatency; + private long mErrorLatency; + private SparseLongArray mErrorLatencyMap; public UsageStats(Context context) { mAuthenticationEvents = new ArrayDeque<>(); - mErrorCount = new SparseIntArray(); - mErrorLatency = new SparseLongArray(); + mErrorFrequencyMap = new SparseIntArray(); + mErrorLatencyMap = new SparseLongArray(); mContext = context; } public void addEvent(AuthenticationEvent event) { + mAuthAttemptCount++; if (mAuthenticationEvents.size() >= EVENT_LOG_SIZE) { mAuthenticationEvents.removeFirst(); } @@ -96,29 +100,38 @@ public class UsageStats { mRejectCount++; mRejectLatency += event.mLatency; } else { - mErrorCount.put(event.mError, mErrorCount.get(event.mError, 0) + 1); - mErrorLatency.put(event.mError, mErrorLatency.get(event.mError, 0L) + event.mLatency); + mErrorCount++; + mErrorLatency += event.mLatency; + mErrorFrequencyMap.put(event.mError, mErrorFrequencyMap.get(event.mError, 0) + 1); + mErrorLatencyMap.put(event.mError, + mErrorLatencyMap.get(event.mError, 0L) + event.mLatency); } } public void print(PrintWriter pw) { - pw.println("Events since last reboot: " + mAuthenticationEvents.size()); + pw.println("Printing most recent events since last reboot(" + + mAuthenticationEvents.size() + " events)"); for (AuthenticationEvent event : mAuthenticationEvents) { pw.println(event.toString(mContext)); } // Dump aggregated usage stats - pw.println("Accept\tCount: " + mAcceptCount + "\tLatency: " + mAcceptLatency + pw.println(""); + pw.println("Accept Count: " + mAcceptCount + "\tLatency: " + mAcceptLatency + "\tAverage: " + (mAcceptCount > 0 ? mAcceptLatency / mAcceptCount : 0)); - pw.println("Reject\tCount: " + mRejectCount + "\tLatency: " + mRejectLatency + pw.println("Reject Count: " + mRejectCount + "\tLatency: " + mRejectLatency + "\tAverage: " + (mRejectCount > 0 ? mRejectLatency / mRejectCount : 0)); - - for (int i = 0; i < mErrorCount.size(); i++) { - final int key = mErrorCount.keyAt(i); - final int count = mErrorCount.get(i); + pw.println("Total Error Count: " + mErrorCount + "\tLatency: " + mErrorLatency + + "\tAverage: " + (mErrorCount > 0 ? mErrorLatency / mErrorCount : 0)); + pw.println("Total Attempts: " + mAuthAttemptCount); + pw.println(""); + + for (int i = 0; i < mErrorFrequencyMap.size(); i++) { + final int key = mErrorFrequencyMap.keyAt(i); + final int count = mErrorFrequencyMap.get(key); pw.println("Error" + key + "\tCount: " + count - + "\tLatency: " + mErrorLatency.get(key, 0L) - + "\tAverage: " + (count > 0 ? mErrorLatency.get(key, 0L) / count : 0) + + "\tLatency: " + mErrorLatencyMap.get(key, 0L) + + "\tAverage: " + (count > 0 ? mErrorLatencyMap.get(key, 0L) / count : 0) + "\t" + FaceManager.getErrorString(mContext, key, 0 /* vendorCode */)); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java index 12f8776a8e18..1435016fc55a 100644 --- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java +++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java @@ -21,6 +21,7 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -73,6 +74,8 @@ public class GnssConfiguration { static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS"; private static final String CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD = "ENABLE_PSDS_PERIODIC_DOWNLOAD"; + private static final String CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL = + "ENABLE_ACTIVE_SIM_EMERGENCY_SUPL"; static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1"; static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2"; static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3"; @@ -207,6 +210,14 @@ public class GnssConfiguration { } /** + * Returns true if during an emergency call, the GnssConfiguration of the activeSubId will be + * injected for the emergency SUPL flow; Returns false otherwise. Default false if not set. + */ + boolean isActiveSimEmergencySuplEnabled() { + return getBooleanConfig(CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL, false); + } + + /** * Returns true if a long-term PSDS server is configured. */ boolean isLongTermPsdsServerConfigured() { @@ -232,16 +243,31 @@ public class GnssConfiguration { /** * Loads the GNSS properties from carrier config file followed by the properties from - * gps debug config file. + * gps debug config file, and injects the GNSS properties into the HAL. */ void reloadGpsProperties() { - if (DEBUG) Log.d(TAG, "Reset GPS properties, previous size = " + mProperties.size()); - loadPropertiesFromCarrierConfig(); + reloadGpsProperties(/* inEmergency= */ false, /* activeSubId= */ -1); + } - String lpp_prof = SystemProperties.get(LPP_PROFILE); - if (!TextUtils.isEmpty(lpp_prof)) { - // override default value of this if lpp_prof is not empty - mProperties.setProperty(CONFIG_LPP_PROFILE, lpp_prof); + /** + * Loads the GNSS properties from carrier config file followed by the properties from + * gps debug config file, and injects the GNSS properties into the HAL. + */ + void reloadGpsProperties(boolean inEmergency, int activeSubId) { + if (DEBUG) { + Log.d(TAG, + "Reset GPS properties, previous size = " + mProperties.size() + ", inEmergency:" + + inEmergency + ", activeSubId=" + activeSubId); + } + loadPropertiesFromCarrierConfig(inEmergency, activeSubId); + + if (isSimAbsent(mContext)) { + // Use the default SIM's LPP profile when SIM is absent. + String lpp_prof = SystemProperties.get(LPP_PROFILE); + if (!TextUtils.isEmpty(lpp_prof)) { + // override default value of this if lpp_prof is not empty + mProperties.setProperty(CONFIG_LPP_PROFILE, lpp_prof); + } } /* @@ -322,16 +348,19 @@ public class GnssConfiguration { /** * Loads GNSS properties from carrier config file. */ - void loadPropertiesFromCarrierConfig() { + void loadPropertiesFromCarrierConfig(boolean inEmergency, int activeSubId) { CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); if (configManager == null) { return; } - int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId(); - PersistableBundle configs = SubscriptionManager.isValidSubscriptionId(ddSubId) - ? configManager.getConfigForSubId(ddSubId) : configManager.getConfig(); + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + if (inEmergency && activeSubId >= 0) { + subId = activeSubId; + } + PersistableBundle configs = SubscriptionManager.isValidSubscriptionId(subId) + ? configManager.getConfigForSubId(subId) : configManager.getConfig(); if (configs == null) { if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config."); configs = CarrierConfigManager.getDefaultConfig(); @@ -422,6 +451,12 @@ public class GnssConfiguration { return gnssConfiguartionIfaceVersion.mMajor < 2; } + private static boolean isSimAbsent(Context context) { + TelephonyManager phone = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + return phone.getSimState() == TelephonyManager.SIM_STATE_ABSENT; + } + private static native HalInterfaceVersion native_get_gnss_configuration_version(); private static native boolean native_set_supl_version(int version); diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index f5c2bbc8d5a2..a6a3db11b729 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -126,6 +126,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * A GNSS implementation of LocationProvider used by LocationManager. @@ -359,8 +360,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } if (isKeepLppProfile) { - // load current properties for the carrier - mGnssConfiguration.loadPropertiesFromCarrierConfig(); + // load current properties for the carrier of ddSubId + mGnssConfiguration.loadPropertiesFromCarrierConfig(/* inEmergency= */ false, + /* activeSubId= */ -1); String lpp_profile = mGnssConfiguration.getLppProfile(); // set the persist property LPP_PROFILE for the value if (lpp_profile != null) { @@ -431,13 +433,38 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // this approach is just fine because events are posted to our handler anyway mGnssConfiguration = mGnssNative.getConfiguration(); // Create a GPS net-initiated handler (also needed by handleInitialize) + GpsNetInitiatedHandler.EmergencyCallCallback emergencyCallCallback = + new GpsNetInitiatedHandler.EmergencyCallCallback() { + + @Override + public void onEmergencyCallStart(int subId) { + if (!mGnssConfiguration.isActiveSimEmergencySuplEnabled()) { + return; + } + mHandler.post(() -> mGnssConfiguration.reloadGpsProperties( + mNIHandler.getInEmergency(), subId)); + } + + @Override + public void onEmergencyCallEnd() { + if (!mGnssConfiguration.isActiveSimEmergencySuplEnabled()) { + return; + } + mHandler.postDelayed(() -> mGnssConfiguration.reloadGpsProperties( + /* inEmergency= */ false, + SubscriptionManager.getDefaultDataSubscriptionId()), + TimeUnit.SECONDS.toMillis(mGnssConfiguration.getEsExtensionSec())); + } + }; mNIHandler = new GpsNetInitiatedHandler(context, mNetInitiatedListener, + emergencyCallCallback, mSuplEsEnabled); // Trigger PSDS data download when the network comes up after booting. mPendingDownloadPsdsTypes.add(GnssPsdsDownloader.LONG_TERM_PSDS_SERVER_INDEX); mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context, - GnssLocationProvider.this::onNetworkAvailable, mHandler.getLooper(), mNIHandler); + GnssLocationProvider.this::onNetworkAvailable, + mHandler.getLooper(), mNIHandler); mNtpTimeHelper = new NtpTimeHelper(mContext, mHandler.getLooper(), this); mGnssSatelliteBlocklistHelper = @@ -1694,9 +1721,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements int type = AGPS_SETID_TYPE_NONE; String setId = null; - int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId(); - if (SubscriptionManager.isValidSubscriptionId(ddSubId)) { - phone = phone.createForSubscriptionId(ddSubId); + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + if (mNIHandler.getInEmergency() && mNetworkConnectivityHandler.getActiveSubId() >= 0) { + subId = mNetworkConnectivityHandler.getActiveSubId(); + } + if (SubscriptionManager.isValidSubscriptionId(subId)) { + phone = phone.createForSubscriptionId(subId); } if ((flags & AGPS_REQUEST_SETID_IMSI) == AGPS_REQUEST_SETID_IMSI) { setId = phone.getSubscriberId(); diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java index aba7572ee1a0..6f890cda5964 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java @@ -190,7 +190,7 @@ class GnssNetworkConnectivityHandler { mContext = context; mGnssNetworkListener = gnssNetworkListener; - SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); + SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); if (subManager != null) { subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); } @@ -311,6 +311,13 @@ class GnssNetworkConnectivityHandler { } /** + * Returns the active Sub ID for emergency SUPL connection. + */ + int getActiveSubId() { + return mActiveSubId; + } + + /** * Called from native code to update AGPS connection status, or to request or release a SUPL * connection. * diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 477b8da61e0f..066692d374d0 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -86,6 +86,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1327,16 +1328,17 @@ public class PreferencesHelper implements RankingConfig { return null; } NotificationChannelGroup group = r.groups.get(groupId).clone(); - group.setChannels(new ArrayList<>()); + ArrayList channels = new ArrayList(); int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { if (groupId.equals(nc.getGroup())) { - group.addChannel(nc); + channels.add(nc); } } } + group.setChannels(channels); return group; } } @@ -1357,44 +1359,48 @@ public class PreferencesHelper implements RankingConfig { public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) { Objects.requireNonNull(pkg); - Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); + List<NotificationChannelGroup> groups = new ArrayList<>(); synchronized (mPackagePreferences) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return ParceledListSlice.emptyList(); } - NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); + Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap(); int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { if (nc.getGroup() != null) { if (r.groups.get(nc.getGroup()) != null) { - NotificationChannelGroup ncg = groups.get(nc.getGroup()); - if (ncg == null) { - ncg = r.groups.get(nc.getGroup()).clone(); - ncg.setChannels(new ArrayList<>()); - groups.put(nc.getGroup(), ncg); - - } - ncg.addChannel(nc); + ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault( + nc.getGroup(), new ArrayList<>()); + channels.add(nc); + groupedChannels.put(nc.getGroup(), channels); } } else { - nonGrouped.addChannel(nc); + ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault( + null, new ArrayList<>()); + channels.add(nc); + groupedChannels.put(null, channels); } } } - if (includeNonGrouped && nonGrouped.getChannels().size() > 0) { - groups.put(null, nonGrouped); - } - if (includeEmpty) { - for (NotificationChannelGroup group : r.groups.values()) { - if (!groups.containsKey(group.getId())) { - groups.put(group.getId(), group); - } + for (NotificationChannelGroup group : r.groups.values()) { + ArrayList<NotificationChannel> channels = + groupedChannels.getOrDefault(group.getId(), new ArrayList<>()); + if (includeEmpty || !channels.isEmpty()) { + NotificationChannelGroup clone = group.clone(); + clone.setChannels(channels); + groups.add(clone); } } - return new ParceledListSlice<>(new ArrayList<>(groups.values())); + + if (includeNonGrouped && groupedChannels.containsKey(null)) { + NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); + nonGrouped.setChannels(groupedChannels.get(null)); + groups.add(nonGrouped); + } + return new ParceledListSlice<>(groups); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0a58044d66c6..a044e60b40ae 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7738,6 +7738,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // relatively fixed. overrideConfig.colorMode = fullConfig.colorMode; overrideConfig.densityDpi = fullConfig.densityDpi; + // The smallest screen width is the short side of screen bounds. Because the bounds + // and density won't be changed, smallestScreenWidthDp is also fixed. + overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp; if (info.isFixedOrientation()) { // lock rotation too. When in size-compat, onConfigurationChanged will watch for and // apply runtime rotation changes. @@ -7834,7 +7837,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // computed accordingly. if (!matchParentBounds()) { getTaskFragment().computeConfigResourceOverrides(resolvedConfig, - newParentConfiguration, areBoundsLetterboxed()); + newParentConfiguration); } // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. @@ -8005,8 +8008,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Since bounds has changed, the configuration needs to be computed accordingly. - getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, - areBoundsLetterboxed()); + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } void recomputeConfiguration() { @@ -8222,7 +8224,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), - newParentConfig, mCompatDisplayInsets, areBoundsLetterboxed()); + newParentConfig); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } @@ -8250,7 +8252,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Compute the configuration based on the resolved bounds. If aspect ratio doesn't // restrict, the bounds should be the requested override bounds. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, - getFixedRotationTransformDisplayInfo(), areBoundsLetterboxed()); + getFixedRotationTransformDisplayInfo()); } } @@ -8314,7 +8316,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // are calculated in compat container space. The actual position on screen will be applied // later, so the calculation is simpler that doesn't need to involve offset from parent. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, - mCompatDisplayInsets, areBoundsLetterboxed()); + mCompatDisplayInsets); // Use current screen layout as source because the size of app is independent to parent. resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride( getConfiguration().screenLayout, resolvedConfig.screenWidthDp, diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 0af046281cc5..1898cc65b107 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -204,8 +204,11 @@ class AsyncRotationController extends FadeAnimationController implements Consume for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { final WindowToken token = mTargetWindowTokens.keyAt(i); for (int j = token.getChildCount() - 1; j >= 0; j--) { - // TODO(b/234585256): The consumer should be handleFinishDrawing(). - token.getChildAt(j).applyWithNextDraw(t -> {}); + // TODO(b/234585256): The consumer should be handleFinishDrawing(). And check why + // the local window might easily time out. + final WindowState w = token.getChildAt(j); + if (w.isClientLocal()) continue; + w.applyWithNextDraw(t -> {}); } } mIsSyncDrawRequested = true; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d9c509c53bb9..90b77f7336c0 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -67,6 +67,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -5574,11 +5575,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private static boolean needsGestureExclusionRestrictions(WindowState win, boolean ignoreRequest) { final int type = win.mAttrs.type; + final int privateFlags = win.mAttrs.privateFlags; final boolean stickyHideNav = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR) && win.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; return (!stickyHideNav || ignoreRequest) && type != TYPE_INPUT_METHOD - && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME; + && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME + && (privateFlags & PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION) == 0; } /** diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 98a51a97110d..4a7a8bd99419 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -47,6 +47,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; @@ -974,6 +975,10 @@ public class DisplayPolicy { } } + if (!win.mSession.mCanSetUnrestrictedGestureExclusion) { + attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; + } + // Check if alternate bars positions were updated. if (mStatusBarAlt == win) { mStatusBarAltPosition = getAltBarPosition(attrs); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index b9d83198139d..3d91921e3ab7 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -549,7 +549,7 @@ public class DisplayRotation { // Go through all tasks and collect them before the rotation // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper // handling is synchronized. - mDisplayContent.mTransitionController.collectForDisplayChange(mDisplayContent, + mDisplayContent.mTransitionController.collectForDisplayAreaChange(mDisplayContent, null /* use collecting transition */); } mService.mAtmService.deferWindowLayout(); diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java index d209f08e6312..64749cf94ddf 100644 --- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java +++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java @@ -101,7 +101,7 @@ public class PhysicalDisplaySwitchTransitionLauncher { if (t != null) { mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); - mTransitionController.collectForDisplayChange(mDisplayContent, t); + mTransitionController.collectForDisplayAreaChange(mDisplayContent, t); mTransition = t; } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index f840171b29b0..ffe3374e6658 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -520,7 +520,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan } private boolean matchesTarget(Task task) { - return task.mUserId == mUserId + return task.getNonFinishingActivityCount() > 0 && task.mUserId == mUserId && task.getBaseIntent().getComponent().equals(mTargetIntent.getComponent()); } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 9b013dac6adf..3577545088e0 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.Manifest.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION; import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY; @@ -109,6 +110,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final boolean mCanCreateSystemApplicationOverlay; final boolean mCanHideNonSystemOverlayWindows; + final boolean mCanSetUnrestrictedGestureExclusion; private AlertWindowNotification mAlertWindowNotification; private boolean mShowingAlertWindowNotificationAllowed; private boolean mClientDead = false; @@ -139,6 +141,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { mSetsUnrestrictedKeepClearAreas = service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS) == PERMISSION_GRANTED; + mCanSetUnrestrictedGestureExclusion = + service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_GESTURE_EXCLUSION) + == PERMISSION_GRANTED; mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications; mDragDropController = mService.mDragDropController; StringBuilder sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 21c5886f085b..f8a9d4665acc 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1957,37 +1957,29 @@ class TaskFragment extends WindowContainer<WindowContainer> { void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Configuration parentConfig) { computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - null /* compatInsets */, false /* areBoundsLetterboxed */); + null /* compatInsets */); } void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, boolean areBoundsLetterboxed) { - computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - null /* compatInsets */, areBoundsLetterboxed); - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo, - boolean areBoundsLetterboxed) { + @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) { if (overrideDisplayInfo != null) { // Make sure the screen related configs can be computed by the provided display info. inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; invalidateAppBoundsConfig(inOutConfig); } computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo, - null /* compatInsets */, areBoundsLetterboxed); + null /* compatInsets */); } void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Configuration parentConfig, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets, - boolean areBoundsLetterboxed) { + @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { if (compatInsets != null) { // Make sure the app bounds can be computed by the compat insets. invalidateAppBoundsConfig(inOutConfig); } computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - compatInsets, areBoundsLetterboxed); + compatInsets); } /** @@ -2014,8 +2006,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { **/ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets, - boolean areBoundsLetterboxed) { + @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = parentConfig.windowConfiguration.getWindowingMode(); @@ -2122,7 +2113,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { : overrideScreenHeightDp; } - // TODO(b/238331848): Consider simplifying logic that computes smallestScreenWidthDp. if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { // When entering to or exiting from Pip, the PipTaskOrganizer will set the @@ -2138,10 +2128,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { // task, because they should not be affected by insets. inOutConfig.smallestScreenWidthDp = (int) (0.5f + Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density); - } else if (isEmbedded() || areBoundsLetterboxed || customContainerPolicy) { - // For embedded TFs and activities that are letteboxed or eligible for size - // compat mode, the smallest width should be updated. Otherwise, inherit from - // the parent task would result in applications loaded wrong resource. + } else if (isEmbedded()) { + // For embedded TFs, the smallest width should be updated. Otherwise, inherit + // from the parent task would result in applications loaded wrong resource. inOutConfig.smallestScreenWidthDp = Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 88572a937156..a02be25bc8d2 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -460,23 +460,26 @@ class TransitionController { * Collects the window containers which need to be synced with the changing display (e.g. * rotating) to the given transition or the current collecting transition. */ - void collectForDisplayChange(@NonNull DisplayContent dc, @Nullable Transition incoming) { + void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc, @Nullable Transition incoming) { if (incoming == null) incoming = mCollectingTransition; if (incoming == null) return; final Transition transition = incoming; // Collect all visible tasks. - dc.forAllLeafTasks(task -> { + wc.forAllLeafTasks(task -> { if (task.isVisible()) { transition.collect(task); } }, true /* traverseTopToBottom */); // Collect all visible non-app windows which need to be drawn before the animation starts. - dc.forAllWindows(w -> { - if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken) - && dc.shouldSyncRotationChange(w)) { - transition.collect(w.mToken); - } - }, true /* traverseTopToBottom */); + final DisplayContent dc = wc.asDisplayContent(); + if (dc != null) { + wc.forAllWindows(w -> { + if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken) + && dc.shouldSyncRotationChange(w)) { + transition.collect(w.mToken); + } + }, true /* traverseTopToBottom */); + } } /** @see Transition#mStatusBarTransitionDelay */ diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 6bb5eceec84e..ee6435493699 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -397,7 +397,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // Go through all tasks and collect them before the rotation // TODO(shell-transitions): move collect() to onConfigurationChange once // wallpaper handling is synchronized. - dc.mTransitionController.collectForDisplayChange(dc, transition); + dc.mTransitionController.collectForDisplayAreaChange(dc, transition); dc.sendNewConfiguration(); effects |= TRANSACT_EFFECTS_LIFECYCLE; } @@ -421,6 +421,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub addToSyncSet(syncId, wc); } if (transition != null) transition.collect(wc); + final DisplayArea da = wc.asDisplayArea(); + // Only check DisplayArea here as a similar thing is done for DisplayContent above. + if (da != null && wc.asDisplayContent() == null + && entry.getValue().getWindowingMode() != da.getWindowingMode()) { + // Go through all tasks and collect them before changing the windowing mode of a + // display-level container. + // TODO(shell-transitions): handle this more elegantly. + da.mTransitionController.collectForDisplayAreaChange(da, transition); + } if ((entry.getValue().getChangeMask() & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 67ef7f5bded8..80de823a6a1b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2357,10 +2357,15 @@ public class AlarmManagerServiceTest { mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, alarmClock); + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), - isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), - eq(EXACT_ALLOW_REASON_COMPAT)); + isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), + bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_COMPAT)); + + final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); + final int type = idleOptions.getTemporaryAppAllowlistType(); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java index 52d0494bd9f0..2f61908aa63d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java @@ -312,4 +312,71 @@ public class BatteryControllerTest { assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); } + + @Test + public void testControllerOnlyTracksPowerJobs() { + JobStatus batteryJob = createJobStatus("testControllerOnlyTracksPowerJobs", + SOURCE_PACKAGE, mSourceUid, + createBaseJobInfoBuilder(1).setRequiresBatteryNotLow(true).build()); + JobStatus chargingJob = createJobStatus("testControllerOnlyTracksPowerJobs", + SOURCE_PACKAGE, mSourceUid, + createBaseJobInfoBuilder(2).setRequiresCharging(true).build()); + JobStatus bothPowerJob = createJobStatus("testControllerOnlyTracksPowerJobs", + SOURCE_PACKAGE, mSourceUid, + createBaseJobInfoBuilder(3) + .setRequiresCharging(true) + .setRequiresBatteryNotLow(true) + .build()); + JobStatus unrelatedJob = createJobStatus("testControllerOnlyTracksPowerJobs", + SOURCE_PACKAGE, mSourceUid, createBaseJobInfoBuilder(4).build()); + + // Follow the lifecycle of tracking + // Start tracking + trackJobs(batteryJob, chargingJob, bothPowerJob, unrelatedJob); + final ArraySet<JobStatus> trackedJobs = mBatteryController.getTrackedJobs(); + final ArraySet<JobStatus> topStartedJobs = mBatteryController.getTopStartedJobs(); + assertTrue(trackedJobs.contains(batteryJob)); + assertTrue(trackedJobs.contains(chargingJob)); + assertTrue(trackedJobs.contains(bothPowerJob)); + assertFalse(trackedJobs.contains(unrelatedJob)); + assertFalse(topStartedJobs.contains(batteryJob)); + assertFalse(topStartedJobs.contains(chargingJob)); + assertFalse(topStartedJobs.contains(bothPowerJob)); + assertFalse(topStartedJobs.contains(unrelatedJob)); + + // Procstate change shouldn't affect anything + setUidBias(mSourceUid, JobInfo.BIAS_TOP_APP); + assertTrue(trackedJobs.contains(batteryJob)); + assertTrue(trackedJobs.contains(chargingJob)); + assertTrue(trackedJobs.contains(bothPowerJob)); + assertFalse(trackedJobs.contains(unrelatedJob)); + assertFalse(topStartedJobs.contains(batteryJob)); + assertFalse(topStartedJobs.contains(chargingJob)); + assertFalse(topStartedJobs.contains(bothPowerJob)); + assertFalse(topStartedJobs.contains(unrelatedJob)); + + // Job starts running + mBatteryController.prepareForExecutionLocked(batteryJob); + mBatteryController.prepareForExecutionLocked(chargingJob); + mBatteryController.prepareForExecutionLocked(bothPowerJob); + mBatteryController.prepareForExecutionLocked(unrelatedJob); + assertTrue(topStartedJobs.contains(batteryJob)); + assertTrue(topStartedJobs.contains(chargingJob)); + assertTrue(topStartedJobs.contains(bothPowerJob)); + assertFalse(topStartedJobs.contains(unrelatedJob)); + + // Job cleanup + mBatteryController.maybeStopTrackingJobLocked(batteryJob, null, false); + mBatteryController.maybeStopTrackingJobLocked(chargingJob, null, false); + mBatteryController.maybeStopTrackingJobLocked(bothPowerJob, null, false); + mBatteryController.maybeStopTrackingJobLocked(unrelatedJob, null, false); + assertFalse(trackedJobs.contains(batteryJob)); + assertFalse(trackedJobs.contains(chargingJob)); + assertFalse(trackedJobs.contains(bothPowerJob)); + assertFalse(trackedJobs.contains(unrelatedJob)); + assertFalse(topStartedJobs.contains(batteryJob)); + assertFalse(topStartedJobs.contains(chargingJob)); + assertFalse(topStartedJobs.contains(bothPowerJob)); + assertFalse(topStartedJobs.contains(unrelatedJob)); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 11a7c7ddf778..1f07b20acc13 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -47,8 +47,10 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -1402,6 +1404,28 @@ public class DisplayContentTests extends WindowTestsBase { win.setHasSurface(false); } + @Test + public void testCalculateSystemGestureExclusion_unrestricted() throws Exception { + mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true; + + final DisplayContent dc = createNewDisplay(); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; + win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + win.getAttrs().privateFlags |= PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; + win.setSystemGestureExclusion(Collections.singletonList(dc.getBounds())); + + performLayout(dc); + + win.setHasSurface(true); + + final Region expected = Region.obtain(); + expected.set(dc.getBounds()); + assertEquals(expected, calculateSystemGestureExclusion(dc)); + + win.setHasSurface(false); + } + @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY}) @Test public void testRequestResizeForEmptyFrames() { diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index f2640d2dc404..324e244c46f5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1496,79 +1496,6 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testComputeConfigResourceOverrides_unresizableApp() { - // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - - final Rect activityBounds = new Rect(mActivity.getBounds()); - - int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp; - int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp; - - // App should launch in fixed orientation letterbox. - // Activity bounds should be 700x1400 with the ratio as the display. - assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); - assertFitted(); - assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp); - assertTrue(originalScreenWidthDp < originalScreenHeighthDp); - - // Rotate display to portrait. - rotateDisplay(mActivity.mDisplayContent, ROTATION_90); - - // After we rotate, the activity should go in the size-compat mode and report the same - // configuration values. - assertScaled(); - assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp); - assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp); - assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp); - - // Restart activity - mActivity.restartProcessIfVisible(); - - // Now configuration should be updated - assertFitted(); - assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp); - assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp); - assertEquals(mActivity.getConfiguration().screenWidthDp, - mActivity.getConfiguration().smallestScreenWidthDp); - } - - @Test - public void testComputeConfigResourceOverrides_resizableFixedOrientationActivity() { - // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - - // Portrait fixed app without max aspect. - prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */); - - final Rect activityBounds = new Rect(mActivity.getBounds()); - - int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp; - int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp; - - // App should launch in fixed orientation letterbox. - // Activity bounds should be 700x1400 with the ratio as the display. - assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); - assertFitted(); - assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp); - assertTrue(originalScreenWidthDp < originalScreenHeighthDp); - - // Rotate display to portrait. - rotateDisplay(mActivity.mDisplayContent, ROTATION_90); - - // Now configuration should be updated - assertFitted(); - assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp); - assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp); - assertEquals(mActivity.getConfiguration().screenWidthDp, - mActivity.getConfiguration().smallestScreenWidthDp); - } - - @Test public void testSplitAspectRatioForUnresizablePortraitApps() { // Set up a display in landscape and ignoring orientation request. int screenWidth = 1600; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 1f03039de72b..f4323db2ceec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -705,8 +705,7 @@ public class TaskTests extends WindowTestsBase { final ActivityRecord.CompatDisplayInsets compatInsets = new ActivityRecord.CompatDisplayInsets( display, activity, /* fixedOrientationBounds= */ null); - task.computeConfigResourceOverrides( - inOutConfig, parentConfig, compatInsets, /* areBoundsLetterboxed */ true); + task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets); assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 1715a295ded3..c8ea70c5d650 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -22,8 +22,8 @@ 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.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1; -import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_2; +import static android.view.InsetsState.ITYPE_BOTTOM_GENERIC_OVERLAY; +import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -1302,40 +1302,42 @@ public class WindowContainerTests extends WindowTestsBase { TYPE_BASE_APPLICATION); attrs2.setTitle("AppWindow2"); activity2.addWindow(createWindowState(attrs2, activity2)); - Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700); - Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200); - - rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); - container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2}); - - InsetsSource navigationBarInsetsProvider1Source = new InsetsSource( - ITYPE_LOCAL_NAVIGATION_BAR_1); - navigationBarInsetsProvider1Source.setFrame(navigationBarInsetsRect1); - navigationBarInsetsProvider1Source.setVisible(true); - InsetsSource navigationBarInsetsProvider2Source = new InsetsSource( - ITYPE_LOCAL_NAVIGATION_BAR_2); - navigationBarInsetsProvider2Source.setFrame(navigationBarInsetsRect2); - navigationBarInsetsProvider2Source.setVisible(true); + Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700); + Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200); + + rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1, + new int[]{ITYPE_TOP_GENERIC_OVERLAY}); + container.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2, + new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY}); + + InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource( + ITYPE_TOP_GENERIC_OVERLAY); + genericOverlayInsetsProvider1Source.setFrame(genericOverlayInsetsRect1); + genericOverlayInsetsProvider1Source.setVisible(true); + InsetsSource genericOverlayInsetsProvider2Source = new InsetsSource( + ITYPE_BOTTOM_GENERIC_OVERLAY); + genericOverlayInsetsProvider2Source.setFrame(genericOverlayInsetsRect2); + genericOverlayInsetsProvider2Source.setVisible(true); activity0.forAllWindows(window -> { - assertEquals(navigationBarInsetsRect1, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame()); + assertEquals(genericOverlayInsetsRect1, + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame()); assertEquals(null, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2)); + window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)); }, true); activity1.forAllWindows(window -> { - assertEquals(navigationBarInsetsRect1, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame()); - assertEquals(navigationBarInsetsRect2, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame()); + assertEquals(genericOverlayInsetsRect1, + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame()); + assertEquals(genericOverlayInsetsRect2, + window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY) + .getFrame()); }, true); activity2.forAllWindows(window -> { - assertEquals(navigationBarInsetsRect1, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame()); - assertEquals(navigationBarInsetsRect2, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame()); + assertEquals(genericOverlayInsetsRect1, + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame()); + assertEquals(genericOverlayInsetsRect2, + window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY) + .getFrame()); }, true); } @@ -1344,7 +1346,7 @@ public class WindowContainerTests extends WindowTestsBase { /* ___ rootTask ________________________________________ | | | - activity0 navigationBarInsetsProvider1 navigationBarInsetsProvider2 + activity0 genericOverlayInsetsProvider1 genericOverlayInsetsProvider2 */ final Task rootTask = createTask(mDisplayContent); @@ -1355,22 +1357,22 @@ public class WindowContainerTests extends WindowTestsBase { attrs.setTitle("AppWindow0"); activity0.addWindow(createWindowState(attrs, activity0)); - Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700); - Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200); + Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700); + Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200); - rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); + rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1, + new int[]{ITYPE_TOP_GENERIC_OVERLAY}); activity0.forAllWindows(window -> { - assertEquals(navigationBarInsetsRect1, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame()); + assertEquals(genericOverlayInsetsRect1, + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame()); }, true); - rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); + rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2, + new int[]{ITYPE_TOP_GENERIC_OVERLAY}); activity0.forAllWindows(window -> { - assertEquals(navigationBarInsetsRect2, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame()); + assertEquals(genericOverlayInsetsRect2, + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame()); }, true); } @@ -1412,30 +1414,32 @@ public class WindowContainerTests extends WindowTestsBase { Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200); rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); + new int[]{ITYPE_TOP_GENERIC_OVERLAY}); container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2}); + new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY}); mDisplayContent.getInsetsStateController().onPostLayout(); - rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); + rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_TOP_GENERIC_OVERLAY}); mDisplayContent.getInsetsStateController().onPostLayout(); activity0.forAllWindows(window -> { assertEquals(null, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1)); + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY)); assertEquals(null, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2)); + window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)); }, true); activity1.forAllWindows(window -> { assertEquals(null, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1)); + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY)); assertEquals(navigationBarInsetsRect2, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame()); + window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY) + .getFrame()); }, true); activity2.forAllWindows(window -> { assertEquals(null, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1)); + window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY)); assertEquals(navigationBarInsetsRect2, - window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame()); + window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY) + .getFrame()); }, true); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 540741217ae8..84c2c551de85 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -30,7 +30,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; -import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1; +import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; @@ -779,11 +779,11 @@ public class WindowOrganizerTests extends WindowTestsBase { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken .toWindowContainerToken(), navigationBarInsetsProviderRect, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); + new int[]{ITYPE_TOP_GENERIC_OVERLAY}); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders - .valueAt(0).getSource().getType()).isEqualTo(ITYPE_LOCAL_NAVIGATION_BAR_1); + .valueAt(0).getSource().getType()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY); } @Test @@ -799,12 +799,12 @@ public class WindowOrganizerTests extends WindowTestsBase { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken .toWindowContainerToken(), navigationBarInsetsProviderRect, - new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); + new int[]{ITYPE_TOP_GENERIC_OVERLAY}); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); final WindowContainerTransaction wct2 = new WindowContainerTransaction(); wct2.removeInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken - .toWindowContainerToken(), new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1}); + .toWindowContainerToken(), new int[]{ITYPE_TOP_GENERIC_OVERLAY}); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2); assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0); |