diff options
72 files changed, 2158 insertions, 478 deletions
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 7020a38ed08a..db06a6ba0ef5 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -48,6 +48,7 @@ import libcore.io.IoUtils; import java.io.FileDescriptor; import java.io.IOException; import java.util.Map; +import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; /** @@ -588,6 +589,8 @@ public class Process { **/ public static final int THREAD_GROUP_RESTRICTED = 7; + /** @hide */ + public static final int SIGNAL_DEFAULT = 0; public static final int SIGNAL_QUIT = 3; public static final int SIGNAL_KILL = 9; public static final int SIGNAL_USR1 = 10; @@ -1437,6 +1440,49 @@ public class Process { sendSignal(pid, SIGNAL_KILL); } + /** + * Check the tgid and tid pair to see if the tid still exists and belong to the tgid. + * + * TOCTOU warning: the status of the tid can change at the time this method returns. This should + * be used in very rare cases such as checking if a (tid, tgid) pair that is known to exist + * recently no longer exists now. As the possibility of the same tid to be reused under the same + * tgid during a short window is rare. And even if it happens the caller logic should be robust + * to handle it without error. + * + * @throws IllegalArgumentException if tgid or tid is not positive. + * @throws SecurityException if the caller doesn't have the permission, this method is expected + * to be used by system process with {@link #SYSTEM_UID} because it + * internally uses tkill(2). + * @throws NoSuchElementException if the Linux process with pid as the tid has exited or it + * doesn't belong to the tgid. + * @hide + */ + public static final void checkTid(int tgid, int tid) + throws IllegalArgumentException, SecurityException, NoSuchElementException { + sendTgSignalThrows(tgid, tid, SIGNAL_DEFAULT); + } + + /** + * Check if the pid still exists. + * + * TOCTOU warning: the status of the pid can change at the time this method returns. This should + * be used in very rare cases such as checking if a pid that belongs to an isolated process of a + * uid known to exist recently no longer exists now. As the possibility of the same pid to be + * reused again under the same uid during a short window is rare. And even if it happens the + * caller logic should be robust to handle it without error. + * + * @throws IllegalArgumentException if pid is not positive. + * @throws SecurityException if the caller doesn't have the permission, this method is expected + * to be used by system process with {@link #SYSTEM_UID} because it + * internally uses kill(2). + * @throws NoSuchElementException if the Linux process with the pid has exited. + * @hide + */ + public static final void checkPid(int pid) + throws IllegalArgumentException, SecurityException, NoSuchElementException { + sendSignalThrows(pid, SIGNAL_DEFAULT); + } + /** @hide */ public static final native int setUid(int uid); @@ -1451,6 +1497,12 @@ public class Process { */ public static final native void sendSignal(int pid, int signal); + private static native void sendSignalThrows(int pid, int signal) + throws IllegalArgumentException, SecurityException, NoSuchElementException; + + private static native void sendTgSignalThrows(int pid, int tgid, int signal) + throws IllegalArgumentException, SecurityException, NoSuchElementException; + /** * @hide * Private impl for avoiding a log message... DO NOT USE without doing diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e26dc73f7172..aad2b4ef9242 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11090,21 +11090,12 @@ public final class Settings { "assist_long_press_home_enabled"; /** - * Whether press and hold on nav handle can trigger search. + * Whether all entrypoints can trigger search. Replaces individual settings. * * @hide */ - public static final String SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED = - "search_press_hold_nav_handle_enabled"; - - /** - * Whether long-pressing on the home button can trigger search. - * - * @hide - */ - public static final String SEARCH_LONG_PRESS_HOME_ENABLED = - "search_long_press_home_enabled"; - + public static final String SEARCH_ALL_ENTRYPOINTS_ENABLED = + "search_all_entrypoints_enabled"; /** * Whether or not the accessibility data streaming is enbled for the diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index e126836020b4..3a90841c5327 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -47,6 +47,19 @@ import java.util.List; * {@hide} */ interface IWindowSession { + + /** + * Bundle key to store the latest sync seq id for the relayout configuration. + * @see #relayout + */ + const String KEY_RELAYOUT_BUNDLE_SEQID = "seqid"; + /** + * Bundle key to store the latest ActivityWindowInfo associated with the relayout configuration. + * Will only be set if the relayout window is an activity window. + * @see #relayout + */ + const String KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO = "activity_window_info"; + int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, int requestedVisibleTypes, out InputChannel outInputChannel, out InsetsState insetsState, @@ -92,7 +105,7 @@ interface IWindowSession { * @param outSurfaceControl Object in which is placed the new display surface. * @param insetsState The current insets state in the system. * @param activeControls Objects which allow controlling {@link InsetsSource}s. - * @param bundle A temporary object to obtain the latest SyncSeqId. + * @param bundle A Bundle to contain the latest SyncSeqId and any extra relayout optional infos. * @return int Result flags, defined in {@link WindowManagerGlobal}. */ int relayout(IWindow window, in WindowManager.LayoutParams attrs, diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cae66720e49e..304e43eaf1c1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -8933,7 +8933,8 @@ public final class ViewRootImpl implements ViewParent, mTempInsets, mTempControls, mRelayoutBundle); mRelayoutRequested = true; - final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid"); + final int maybeSyncSeqId = mRelayoutBundle.getInt( + IWindowSession.KEY_RELAYOUT_BUNDLE_SEQID); if (maybeSyncSeqId > 0) { mSyncSeqId = maybeSyncSeqId; } diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 7e77f150b63b..43df4f962256 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -112,10 +112,13 @@ public final class TaskFragmentOperation implements Parcelable { /** * Creates a decor surface in the parent Task of the TaskFragment. The created decor surface * will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED} - * event callback. The decor surface can be used to draw the divider between TaskFragments or - * other decorations. + * event callback. If a decor surface already exists in the parent Task, the current + * TaskFragment will become the new owner of the decor surface and the decor surface will be + * moved above the TaskFragment. + * + * The decor surface can be used to draw the divider between TaskFragments or other decorations. */ - public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14; + public static final int OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE = 14; /** * Removes the decor surface in the parent Task of the TaskFragment. @@ -162,7 +165,7 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_ISOLATED_NAVIGATION, OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, OP_TYPE_REORDER_TO_TOP_OF_TASK, - OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE, + OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE, OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE, OP_TYPE_SET_DIM_ON_TASK, OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH, diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 14fb17c09031..65bf24179bea 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -38,6 +38,17 @@ flag { } flag { + name: "skip_sleeping_when_switching_display" + namespace: "windowing_frontend" + description: "Reduce unnecessary visibility or lifecycle changes when changing fold state" + bug: "303241079" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "introduce_smoother_dimmer" namespace: "windowing_frontend" description: "Refactor dim to fix flickers" diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index d2e58bb62c46..982189e30beb 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -1137,6 +1137,41 @@ void android_os_Process_sendSignalQuiet(JNIEnv* env, jobject clazz, jint pid, ji } } +void android_os_Process_sendSignalThrows(JNIEnv* env, jobject clazz, jint pid, jint sig) { + if (pid <= 0) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid argument: pid(%d)", + pid); + return; + } + int ret = kill(pid, sig); + if (ret < 0) { + if (errno == ESRCH) { + jniThrowExceptionFmt(env, "java/util/NoSuchElementException", + "Process with pid %d not found", pid); + } else { + signalExceptionForError(env, errno, pid); + } + } +} + +void android_os_Process_sendTgSignalThrows(JNIEnv* env, jobject clazz, jint tgid, jint tid, + jint sig) { + if (tgid <= 0 || tid <= 0) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid argument: tgid(%d), tid(%d)", tid, tgid); + return; + } + int ret = tgkill(tgid, tid, sig); + if (ret < 0) { + if (errno == ESRCH) { + jniThrowExceptionFmt(env, "java/util/NoSuchElementException", + "Process with tid %d and tgid %d not found", tid, tgid); + } else { + signalExceptionForError(env, errno, tid); + } + } +} + static jlong android_os_Process_getElapsedCpuTime(JNIEnv* env, jobject clazz) { struct timespec ts; @@ -1357,6 +1392,8 @@ static const JNINativeMethod methods[] = { {"setGid", "(I)I", (void*)android_os_Process_setGid}, {"sendSignal", "(II)V", (void*)android_os_Process_sendSignal}, {"sendSignalQuiet", "(II)V", (void*)android_os_Process_sendSignalQuiet}, + {"sendSignalThrows", "(II)V", (void*)android_os_Process_sendSignalThrows}, + {"sendTgSignalThrows", "(III)V", (void*)android_os_Process_sendTgSignalThrows}, {"setProcessFrozen", "(IIZ)V", (void*)android_os_Process_setProcessFrozen}, {"getFreeMemory", "()J", (void*)android_os_Process_getFreeMemory}, {"getTotalMemory", "()J", (void*)android_os_Process_getTotalMemory}, diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 763d9ce1a053..6b0c2d28b776 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -143,9 +143,11 @@ message SecureSettingsProto { optional SettingProto gesture_setup_complete = 9 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ]; - optional SettingProto search_press_hold_nav_handle_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ]; - optional SettingProto search_long_press_home_enabled = 13 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Deprecated - use search_all_entrypoints_enabled instead + optional SettingProto search_press_hold_nav_handle_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC, deprecated = true ]; + optional SettingProto search_long_press_home_enabled = 13 [ (android.privacy).dest = DEST_AUTOMATIC, deprecated = true ]; optional SettingProto visual_query_accessibility_detection_enabled = 14 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_all_entrypoints_enabled = 15 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Assist assist = 7; diff --git a/core/res/res/drawable/activity_embedding_divider_handle.xml b/core/res/res/drawable/activity_embedding_divider_handle.xml new file mode 100644 index 000000000000..d9f363cb33a7 --- /dev/null +++ b/core/res/res/drawable/activity_embedding_divider_handle.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" + android:drawable="@drawable/activity_embedding_divider_handle_pressed" /> + <item android:drawable="@drawable/activity_embedding_divider_handle_default" /> +</selector>
\ No newline at end of file diff --git a/core/res/res/drawable/activity_embedding_divider_handle_default.xml b/core/res/res/drawable/activity_embedding_divider_handle_default.xml new file mode 100644 index 000000000000..565f67169ab5 --- /dev/null +++ b/core/res/res/drawable/activity_embedding_divider_handle_default.xml @@ -0,0 +1,23 @@ +<!-- + Copyright 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="@dimen/activity_embedding_divider_handle_radius" /> + <size + android:width="@dimen/activity_embedding_divider_handle_width" + android:height="@dimen/activity_embedding_divider_handle_height" /> + <solid android:color="@color/activity_embedding_divider_color" /> +</shape>
\ No newline at end of file diff --git a/core/res/res/drawable/activity_embedding_divider_handle_pressed.xml b/core/res/res/drawable/activity_embedding_divider_handle_pressed.xml new file mode 100644 index 000000000000..e5cca2397806 --- /dev/null +++ b/core/res/res/drawable/activity_embedding_divider_handle_pressed.xml @@ -0,0 +1,23 @@ +<!-- + Copyright 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="@dimen/activity_embedding_divider_handle_radius_pressed" /> + <size + android:width="@dimen/activity_embedding_divider_handle_width_pressed" + android:height="@dimen/activity_embedding_divider_handle_height_pressed" /> + <solid android:color="@color/activity_embedding_divider_color_pressed" /> +</shape>
\ No newline at end of file diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 417c6df1e30d..e6719195565e 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -593,6 +593,10 @@ <color name="accessibility_magnification_thumbnail_container_background_color">#99000000</color> <color name="accessibility_magnification_thumbnail_container_stroke_color">#FFFFFF</color> + <!-- Activity Embedding divider --> + <color name="activity_embedding_divider_color">#8e918f</color> + <color name="activity_embedding_divider_color_pressed">#e3e3e3</color> + <!-- Lily Language Picker language item view colors --> <color name="language_picker_item_text_color">#202124</color> <color name="language_picker_item_text_color_secondary">#5F6368</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index efba7099d678..89ac81ebce56 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6419,10 +6419,8 @@ <!-- Default value for Settings.ASSIST_TOUCH_GESTURE_ENABLED --> <bool name="config_assistTouchGestureEnabledDefault">true</bool> - <!-- Default value for Settings.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED --> - <bool name="config_searchPressHoldNavHandleEnabledDefault">true</bool> - <!-- Default value for Settings.ASSIST_LONG_PRESS_HOME_ENABLED for search overlay --> - <bool name="config_searchLongPressHomeEnabledDefault">true</bool> + <!-- Default value for Settings.SEARCH_ALL_ENTRYPOINTS_ENABLED --> + <bool name="config_searchAllEntrypointsEnabledDefault">true</bool> <!-- The maximum byte size of the information contained in the bundle of HotwordDetectedResult. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 291a5936330a..4aa741de80a5 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -1028,6 +1028,16 @@ <dimen name="popup_enter_animation_from_y_delta">20dp</dimen> <dimen name="popup_exit_animation_to_y_delta">-10dp</dimen> + <!-- Dimensions for the activity embedding divider. --> + <dimen name="activity_embedding_divider_handle_width">4dp</dimen> + <dimen name="activity_embedding_divider_handle_height">48dp</dimen> + <dimen name="activity_embedding_divider_handle_radius">2dp</dimen> + <dimen name="activity_embedding_divider_handle_width_pressed">12dp</dimen> + <dimen name="activity_embedding_divider_handle_height_pressed">53dp</dimen> + <dimen name="activity_embedding_divider_handle_radius_pressed">6dp</dimen> + <dimen name="activity_embedding_divider_touch_target_width">24dp</dimen> + <dimen name="activity_embedding_divider_touch_target_height">64dp</dimen> + <!-- Default handwriting bounds offsets for editors. --> <dimen name="handwriting_bounds_offset_left">10dp</dimen> <dimen name="handwriting_bounds_offset_top">40dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c2fa297ea984..2e029b23f6af 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5019,8 +5019,7 @@ <java-symbol type="bool" name="config_assistLongPressHomeEnabledDefault" /> <java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" /> - <java-symbol type="bool" name="config_searchPressHoldNavHandleEnabledDefault" /> - <java-symbol type="bool" name="config_searchLongPressHomeEnabledDefault" /> + <java-symbol type="bool" name="config_searchAllEntrypointsEnabledDefault" /> <java-symbol type="integer" name="config_hotwordDetectedResultMaxBundleSize" /> @@ -5338,6 +5337,11 @@ <java-symbol type="raw" name="default_ringtone_vibration_effect" /> + <!-- For activity embedding divider --> + <java-symbol type="drawable" name="activity_embedding_divider_handle" /> + <java-symbol type="dimen" name="activity_embedding_divider_touch_target_width" /> + <java-symbol type="dimen" name="activity_embedding_divider_touch_target_height" /> + <!-- Whether we order unlocking and waking --> <java-symbol type="bool" name="config_orderUnlockAndWake" /> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index 97562783882c..16c77d0c3c81 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -53,7 +53,7 @@ class WindowExtensionsImpl implements WindowExtensions { * The min version of the WM Extensions that must be supported in the current platform version. */ @VisibleForTesting - static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 5; + static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 6; private final Object mLock = new Object(); private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 100185b84b77..cae232e54f3c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -17,6 +17,12 @@ package androidx.window.extensions.embedding; import static android.util.TypedValue.COMPLEX_UNIT_DIP; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE; +import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; import static androidx.window.extensions.embedding.DividerAttributes.RATIO_UNSET; import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_UNSET; @@ -28,34 +34,253 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RotateDrawable; +import android.hardware.display.DisplayManager; +import android.os.IBinder; import android.util.TypedValue; +import android.view.Gravity; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.window.InputTransferToken; +import android.window.TaskFragmentOperation; +import android.window.TaskFragmentParentInfo; +import android.window.WindowContainerTransaction; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; +import java.util.Objects; + /** * Manages the rendering and interaction of the divider. */ class DividerPresenter { + private static final String WINDOW_NAME = "AE Divider"; + // TODO(b/327067596) Update based on UX guidance. - @VisibleForTesting static final float DEFAULT_MIN_RATIO = 0.35f; - @VisibleForTesting static final float DEFAULT_MAX_RATIO = 0.65f; - @VisibleForTesting static final int DEFAULT_DIVIDER_WIDTH_DP = 24; + private static final Color DEFAULT_DIVIDER_COLOR = Color.valueOf(Color.BLACK); + @VisibleForTesting + static final float DEFAULT_MIN_RATIO = 0.35f; + @VisibleForTesting + static final float DEFAULT_MAX_RATIO = 0.65f; + @VisibleForTesting + static final int DEFAULT_DIVIDER_WIDTH_DP = 24; + + /** + * The {@link Properties} of the divider. This field is {@code null} when no divider should be + * drawn, e.g. when the split doesn't have {@link DividerAttributes} or when the decor surface + * is not available. + */ + @Nullable + @VisibleForTesting + Properties mProperties; + + /** + * The {@link Renderer} of the divider. This field is {@code null} when no divider should be + * drawn, i.e. when {@link #mProperties} is {@code null}. The {@link Renderer} is recreated or + * updated when {@link #mProperties} is changed. + */ + @Nullable + @VisibleForTesting + Renderer mRenderer; + + /** + * The owner TaskFragment token of the decor surface. The decor surface is placed right above + * the owner TaskFragment surface and is removed if the owner TaskFragment is destroyed. + */ + @Nullable + @VisibleForTesting + IBinder mDecorSurfaceOwner; + + /** Updates the divider when external conditions are changed. */ + void updateDivider( + @NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentParentInfo parentInfo, + @Nullable SplitContainer topSplitContainer) { + if (!Flags.activityEmbeddingInteractiveDividerFlag()) { + return; + } + + // Clean up the decor surface if top SplitContainer is null. + if (topSplitContainer == null) { + removeDecorSurfaceAndDivider(wct); + return; + } + + // Clean up the decor surface if DividerAttributes is null. + final DividerAttributes dividerAttributes = + topSplitContainer.getCurrentSplitAttributes().getDividerAttributes(); + if (dividerAttributes == null) { + removeDecorSurfaceAndDivider(wct); + return; + } + + if (topSplitContainer.getCurrentSplitAttributes().getSplitType() + instanceof SplitAttributes.SplitType.ExpandContainersSplitType) { + // No divider is needed for ExpandContainersSplitType. + removeDivider(); + return; + } + + // Skip updating when the TFs have not been updated to match the SplitAttributes. + if (topSplitContainer.getPrimaryContainer().getLastRequestedBounds().isEmpty() + || topSplitContainer.getSecondaryContainer().getLastRequestedBounds().isEmpty()) { + return; + } + + final SurfaceControl decorSurface = parentInfo.getDecorSurface(); + if (decorSurface == null) { + // Clean up when the decor surface is currently unavailable. + removeDivider(); + // Request to create the decor surface + createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer()); + return; + } + + // make the top primary container the owner of the decor surface. + if (!Objects.equals(mDecorSurfaceOwner, + topSplitContainer.getPrimaryContainer().getTaskFragmentToken())) { + createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer()); + } + + updateProperties( + new Properties( + parentInfo.getConfiguration(), + dividerAttributes, + decorSurface, + getInitialDividerPosition(topSplitContainer), + isVerticalSplit(topSplitContainer), + parentInfo.getDisplayId())); + } + + private void updateProperties(@NonNull Properties properties) { + if (Properties.equalsForDivider(mProperties, properties)) { + return; + } + final Properties previousProperties = mProperties; + mProperties = properties; + + if (mRenderer == null) { + // Create a new renderer when a renderer doesn't exist yet. + mRenderer = new Renderer(); + } else if (!Properties.areSameSurfaces( + previousProperties.mDecorSurface, mProperties.mDecorSurface) + || previousProperties.mDisplayId != mProperties.mDisplayId) { + // Release and recreate the renderer if the decor surface or the display has changed. + mRenderer.release(); + mRenderer = new Renderer(); + } else { + // Otherwise, update the renderer for the new properties. + mRenderer.update(); + } + } + + /** + * Creates a decor surface for the TaskFragment if no decor surface exists, or changes the owner + * of the existing decor surface to be the specified TaskFragment. + * + * See {@link TaskFragmentOperation#OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE}. + */ + private void createOrMoveDecorSurface( + @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE) + .build(); + wct.addTaskFragmentOperation(container.getTaskFragmentToken(), operation); + mDecorSurfaceOwner = container.getTaskFragmentToken(); + } + + private void removeDecorSurfaceAndDivider(@NonNull WindowContainerTransaction wct) { + if (mDecorSurfaceOwner != null) { + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) + .build(); + wct.addTaskFragmentOperation(mDecorSurfaceOwner, operation); + mDecorSurfaceOwner = null; + } + removeDivider(); + } + + private void removeDivider() { + if (mRenderer != null) { + mRenderer.release(); + } + mProperties = null; + mRenderer = null; + } + + @VisibleForTesting + static int getInitialDividerPosition(@NonNull SplitContainer splitContainer) { + final Rect primaryBounds = + splitContainer.getPrimaryContainer().getLastRequestedBounds(); + final Rect secondaryBounds = + splitContainer.getSecondaryContainer().getLastRequestedBounds(); + if (isVerticalSplit(splitContainer)) { + return Math.min(primaryBounds.right, secondaryBounds.right); + } else { + return Math.min(primaryBounds.bottom, secondaryBounds.bottom); + } + } + + private static boolean isVerticalSplit(@NonNull SplitContainer splitContainer) { + final int layoutDirection = splitContainer.getCurrentSplitAttributes().getLayoutDirection(); + switch(layoutDirection) { + case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: + case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: + case SplitAttributes.LayoutDirection.LOCALE: + return true; + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: + return false; + default: + throw new IllegalArgumentException("Invalid layout direction:" + layoutDirection); + } + } - static int getDividerWidthPx(@NonNull DividerAttributes dividerAttributes) { + private static void safeReleaseSurfaceControl(@Nullable SurfaceControl sc) { + if (sc != null) { + sc.release(); + } + } + + private static int getDividerWidthPx(@NonNull DividerAttributes dividerAttributes) { int dividerWidthDp = dividerAttributes.getWidthDp(); + return convertDpToPixel(dividerWidthDp); + } + private static int convertDpToPixel(int dp) { // TODO(b/329193115) support divider on secondary display final Context applicationContext = ActivityThread.currentActivityThread().getApplication(); return (int) TypedValue.applyDimension( COMPLEX_UNIT_DIP, - dividerWidthDp, + dp, applicationContext.getResources().getDisplayMetrics()); } + private static int getDimensionDp(@IdRes int resId) { + final Context context = ActivityThread.currentActivityThread().getApplication(); + final int px = context.getResources().getDimensionPixelSize(resId); + return (int) TypedValue.convertPixelsToDimension( + COMPLEX_UNIT_DIP, + px, + context.getResources().getDisplayMetrics()); + } + /** * Returns the container bound offset that is a result of the presence of a divider. * @@ -140,6 +365,12 @@ class DividerPresenter { widthDp = DEFAULT_DIVIDER_WIDTH_DP; } + if (dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { + // Draggable divider width must be larger than the drag handle size. + widthDp = Math.max(widthDp, + getDimensionDp(R.dimen.activity_embedding_divider_touch_target_width)); + } + float minRatio = dividerAttributes.getPrimaryMinRatio(); if (minRatio == RATIO_UNSET) { minRatio = DEFAULT_MIN_RATIO; @@ -156,4 +387,231 @@ class DividerPresenter { .setPrimaryMaxRatio(maxRatio) .build(); } + + /** + * Properties for the {@link DividerPresenter}. The rendering of the divider solely depends on + * these properties. When any value is updated, the divider is re-rendered. The Properties + * instance is created only when all the pre-conditions of drawing a divider are met. + */ + @VisibleForTesting + static class Properties { + private static final int CONFIGURATION_MASK_FOR_DIVIDER = + ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + @NonNull + private final Configuration mConfiguration; + @NonNull + private final DividerAttributes mDividerAttributes; + @NonNull + private final SurfaceControl mDecorSurface; + + /** The initial position of the divider calculated based on container bounds. */ + private final int mInitialDividerPosition; + + /** Whether the split is vertical, such as left-to-right or right-to-left split. */ + private final boolean mIsVerticalSplit; + + private final int mDisplayId; + + @VisibleForTesting + Properties( + @NonNull Configuration configuration, + @NonNull DividerAttributes dividerAttributes, + @NonNull SurfaceControl decorSurface, + int initialDividerPosition, + boolean isVerticalSplit, + int displayId) { + mConfiguration = configuration; + mDividerAttributes = dividerAttributes; + mDecorSurface = decorSurface; + mInitialDividerPosition = initialDividerPosition; + mIsVerticalSplit = isVerticalSplit; + mDisplayId = displayId; + } + + /** + * Compares whether two Properties objects are equal for rendering the divider. The + * Configuration is checked for rendering related fields, and other fields are checked for + * regular equality. + */ + private static boolean equalsForDivider(@Nullable Properties a, @Nullable Properties b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return areSameSurfaces(a.mDecorSurface, b.mDecorSurface) + && Objects.equals(a.mDividerAttributes, b.mDividerAttributes) + && areConfigurationsEqualForDivider(a.mConfiguration, b.mConfiguration) + && a.mInitialDividerPosition == b.mInitialDividerPosition + && a.mIsVerticalSplit == b.mIsVerticalSplit + && a.mDisplayId == b.mDisplayId; + } + + private static boolean areSameSurfaces( + @Nullable SurfaceControl sc1, @Nullable SurfaceControl sc2) { + if (sc1 == sc2) { + // If both are null or both refer to the same object. + return true; + } + if (sc1 == null || sc2 == null) { + return false; + } + return sc1.isSameSurface(sc2); + } + + private static boolean areConfigurationsEqualForDivider( + @NonNull Configuration a, @NonNull Configuration b) { + final int diff = a.diff(b); + return (diff & CONFIGURATION_MASK_FOR_DIVIDER) == 0; + } + } + + /** + * Handles the rendering of the divider. When the decor surface is updated, the renderer is + * recreated. When other fields in the Properties are changed, the renderer is updated. + */ + @VisibleForTesting + class Renderer { + @NonNull + private final SurfaceControl mDividerSurface; + @NonNull + private final WindowlessWindowManager mWindowlessWindowManager; + @NonNull + private final SurfaceControlViewHost mViewHost; + @NonNull + private final FrameLayout mDividerLayout; + private final int mDividerWidthPx; + + private Renderer() { + mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes); + + mDividerSurface = createChildSurface("DividerSurface", true /* visible */); + mWindowlessWindowManager = new WindowlessWindowManager( + mProperties.mConfiguration, + mDividerSurface, + new InputTransferToken()); + + final Context context = ActivityThread.currentActivityThread().getApplication(); + final DisplayManager displayManager = context.getSystemService(DisplayManager.class); + mViewHost = new SurfaceControlViewHost( + context, displayManager.getDisplay(mProperties.mDisplayId), + mWindowlessWindowManager, "DividerContainer"); + mDividerLayout = new FrameLayout(context); + + update(); + } + + /** Updates the divider when properties are changed */ + @VisibleForTesting + void update() { + mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration); + updateSurface(); + updateLayout(); + updateDivider(); + } + + @VisibleForTesting + void release() { + mViewHost.release(); + // TODO handle synchronization between surface transactions and WCT. + new SurfaceControl.Transaction().remove(mDividerSurface).apply(); + safeReleaseSurfaceControl(mDividerSurface); + } + + private void updateSurface() { + final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); + // TODO handle synchronization between surface transactions and WCT. + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + if (mProperties.mIsVerticalSplit) { + t.setPosition(mDividerSurface, mProperties.mInitialDividerPosition, 0.0f); + t.setWindowCrop(mDividerSurface, mDividerWidthPx, taskBounds.height()); + } else { + t.setPosition(mDividerSurface, 0.0f, mProperties.mInitialDividerPosition); + t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerWidthPx); + } + t.apply(); + } + + private void updateLayout() { + final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); + final WindowManager.LayoutParams lp = mProperties.mIsVerticalSplit + ? new WindowManager.LayoutParams( + mDividerWidthPx, + taskBounds.height(), + TYPE_APPLICATION_PANEL, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY, + PixelFormat.TRANSLUCENT) + : new WindowManager.LayoutParams( + taskBounds.width(), + mDividerWidthPx, + TYPE_APPLICATION_PANEL, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY, + PixelFormat.TRANSLUCENT); + lp.setTitle(WINDOW_NAME); + mViewHost.setView(mDividerLayout, lp); + } + + private void updateDivider() { + mDividerLayout.removeAllViews(); + mDividerLayout.setBackgroundColor(DEFAULT_DIVIDER_COLOR.toArgb()); + if (mProperties.mDividerAttributes.getDividerType() + == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { + drawDragHandle(); + } + mViewHost.getView().invalidate(); + } + + private void drawDragHandle() { + final Context context = mDividerLayout.getContext(); + final ImageButton button = new ImageButton(context); + final FrameLayout.LayoutParams params = mProperties.mIsVerticalSplit + ? new FrameLayout.LayoutParams( + context.getResources().getDimensionPixelSize( + R.dimen.activity_embedding_divider_touch_target_width), + context.getResources().getDimensionPixelSize( + R.dimen.activity_embedding_divider_touch_target_height)) + : new FrameLayout.LayoutParams( + context.getResources().getDimensionPixelSize( + R.dimen.activity_embedding_divider_touch_target_height), + context.getResources().getDimensionPixelSize( + R.dimen.activity_embedding_divider_touch_target_width)); + params.gravity = Gravity.CENTER; + button.setLayoutParams(params); + button.setBackgroundColor(R.color.transparent); + + final Drawable handle = context.getResources().getDrawable( + R.drawable.activity_embedding_divider_handle, context.getTheme()); + if (mProperties.mIsVerticalSplit) { + button.setImageDrawable(handle); + } else { + // Rotate the handle drawable + RotateDrawable rotatedHandle = new RotateDrawable(); + rotatedHandle.setFromDegrees(90f); + rotatedHandle.setToDegrees(90f); + rotatedHandle.setPivotXRelative(true); + rotatedHandle.setPivotYRelative(true); + rotatedHandle.setPivotX(0.5f); + rotatedHandle.setPivotY(0.5f); + rotatedHandle.setLevel(1); + rotatedHandle.setDrawable(handle); + + button.setImageDrawable(rotatedHandle); + } + mDividerLayout.addView(button); + } + + @NonNull + private SurfaceControl createChildSurface(@NonNull String name, boolean visible) { + final Rect bounds = mProperties.mConfiguration.windowConfiguration.getBounds(); + return new SurfaceControl.Builder() + .setParent(mProperties.mDecorSurface) + .setName(name) + .setHidden(!visible) + .setCallsite("DividerManager.createChildSurface") + .setBufferSize(bounds.width(), bounds.height()) + .setColorLayer() + .build(); + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 80afb16d5832..3f4dddf0cc81 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -168,11 +168,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * @param fragmentToken token of an existing TaskFragment. */ void expandTaskFragment(@NonNull WindowContainerTransaction wct, - @NonNull IBinder fragmentToken) { + @NonNull TaskFragmentContainer container) { + final IBinder fragmentToken = container.getTaskFragmentToken(); resizeTaskFragment(wct, fragmentToken, new Rect()); clearAdjacentTaskFragments(wct, fragmentToken); updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); + + container.getTaskContainer().updateDivider(wct); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 0cc4b1f367d8..1bc8264d8e7e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -844,6 +844,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Checks if container should be updated before apply new parentInfo. final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo); taskContainer.updateTaskFragmentParentInfo(parentInfo); + taskContainer.updateDivider(wct); // If the last direct activity of the host task is dismissed and the overlay container is // the only taskFragment, the overlay container should also be dismissed. @@ -1224,7 +1225,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer container = getContainerWithActivity(activity); if (shouldContainerBeExpanded(container)) { // Make sure that the existing container is expanded. - mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken()); + mPresenter.expandTaskFragment(wct, container); } else { // Put activity into a new expanded container. final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity)); @@ -1928,7 +1929,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } if (shouldContainerBeExpanded(container)) { if (container.getInfo() != null) { - mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken()); + mPresenter.expandTaskFragment(wct, container); } // If the info is not available yet the task fragment will be expanded when it's ready return; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index f680694c3af9..20bc82002339 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -368,6 +368,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes); updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); + taskContainer.updateDivider(wct); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @@ -686,8 +687,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { splitContainer.getPrimaryContainer().getTaskFragmentToken(); final IBinder secondaryToken = splitContainer.getSecondaryContainer().getTaskFragmentToken(); - expandTaskFragment(wct, primaryToken); - expandTaskFragment(wct, secondaryToken); + expandTaskFragment(wct, splitContainer.getPrimaryContainer()); + expandTaskFragment(wct, splitContainer.getSecondaryContainer()); // Set the companion TaskFragment when the two containers stacked. setCompanionTaskFragment(wct, primaryToken, secondaryToken, splitContainer.getSplitRule(), true /* isStacked */); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 73109e266905..e75a317cc3b3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -77,6 +77,9 @@ class TaskContainer { private boolean mHasDirectActivity; + @Nullable + private TaskFragmentParentInfo mTaskFragmentParentInfo; + /** * TaskFragments that the organizer has requested to be closed. They should be removed when * the organizer receives @@ -85,14 +88,17 @@ class TaskContainer { */ final Set<IBinder> mFinishedContainer = new ArraySet<>(); + // TODO(b/293654166): move DividerPresenter to SplitController. + @NonNull + final DividerPresenter mDividerPresenter; + /** * The {@link TaskContainer} constructor * - * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with - * {@code activityInTask}. + * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with + * {@code activityInTask}. * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to * initialize the {@link TaskContainer} properties. - * */ TaskContainer(int taskId, @NonNull Activity activityInTask) { if (taskId == INVALID_TASK_ID) { @@ -107,6 +113,7 @@ class TaskContainer { // the host task is visible and has an activity in the task. mIsVisible = true; mHasDirectActivity = true; + mDividerPresenter = new DividerPresenter(); } int getTaskId() { @@ -136,10 +143,12 @@ class TaskContainer { } void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { + // TODO(b/293654166): cache the TaskFragmentParentInfo and remove these fields. mConfiguration.setTo(info.getConfiguration()); mDisplayId = info.getDisplayId(); mIsVisible = info.isVisible(); mHasDirectActivity = info.hasDirectActivity(); + mTaskFragmentParentInfo = info; } /** @@ -161,8 +170,8 @@ class TaskContainer { * Returns the windowing mode for the TaskFragments below this Task, which should be split with * other TaskFragments. * - * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when - * the pair of TaskFragments are stacked due to the limited space. + * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when + * the pair of TaskFragments are stacked due to the limited space. */ @WindowingMode int getWindowingModeForTaskFragment(@Nullable Rect taskFragmentBounds) { @@ -228,7 +237,7 @@ class TaskContainer { @Nullable TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin, - boolean includeOverlay) { + boolean includeOverlay) { for (int i = mContainers.size() - 1; i >= 0; i--) { final TaskFragmentContainer container = mContainers.get(i); if (!includePin && isTaskFragmentContainerPinned(container)) { @@ -283,7 +292,7 @@ class TaskContainer { return mContainers.indexOf(child); } - /** Whether the Task is in an intermediate state waiting for the server update.*/ + /** Whether the Task is in an intermediate state waiting for the server update. */ boolean isInIntermediateState() { for (TaskFragmentContainer container : mContainers) { if (container.isInIntermediateState()) { @@ -389,6 +398,26 @@ class TaskContainer { return mContainers; } + void updateDivider(@NonNull WindowContainerTransaction wct) { + if (mTaskFragmentParentInfo != null) { + // Update divider only if TaskFragmentParentInfo is available. + mDividerPresenter.updateDivider( + wct, mTaskFragmentParentInfo, getTopNonFinishingSplitContainer()); + } + } + + @Nullable + private SplitContainer getTopNonFinishingSplitContainer() { + for (int i = mSplitContainers.size() - 1; i >= 0; i--) { + final SplitContainer splitContainer = mSplitContainers.get(i); + if (!splitContainer.getPrimaryContainer().isFinished() + && !splitContainer.getSecondaryContainer().isFinished()) { + return splitContainer; + } + } + return null; + } + private void onTaskFragmentContainerUpdated() { // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce // another special container that should also be on top in the future. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index a6bf99d4add5..e20a3e02c65d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -748,6 +748,10 @@ class TaskFragmentContainer { } } + @NonNull Rect getLastRequestedBounds() { + return mLastRequestedBounds; + } + /** * Checks if last requested windowing mode is equal to the provided value. * @see WindowContainerTransaction#setWindowingMode diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 2a277f4c9619..4d1d807038eb 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -16,22 +16,49 @@ package androidx.window.extensions.embedding; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE; +import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; + import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider; +import static androidx.window.extensions.embedding.DividerPresenter.getInitialDividerPosition; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; +import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; +import android.view.Display; +import android.view.SurfaceControl; +import android.window.TaskFragmentOperation; +import android.window.TaskFragmentParentInfo; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Test class for {@link DividerPresenter}. @@ -43,6 +70,167 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class DividerPresenterTest { + @Rule + public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); + + @Mock + private DividerPresenter.Renderer mRenderer; + + @Mock + private WindowContainerTransaction mTransaction; + + @Mock + private TaskFragmentParentInfo mParentInfo; + + @Mock + private SplitContainer mSplitContainer; + + @Mock + private SurfaceControl mSurfaceControl; + + private DividerPresenter mDividerPresenter; + + private final IBinder mPrimaryContainerToken = new Binder(); + + private final IBinder mSecondaryContainerToken = new Binder(); + + private final IBinder mAnotherContainerToken = new Binder(); + + private DividerPresenter.Properties mProperties; + + private static final DividerAttributes DEFAULT_DIVIDER_ATTRIBUTES = + new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE).build(); + + private static final DividerAttributes ANOTHER_DIVIDER_ATTRIBUTES = + new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE) + .setWidthDp(10).build(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG); + + when(mParentInfo.getDisplayId()).thenReturn(Display.DEFAULT_DISPLAY); + when(mParentInfo.getConfiguration()).thenReturn(new Configuration()); + when(mParentInfo.getDecorSurface()).thenReturn(mSurfaceControl); + + when(mSplitContainer.getCurrentSplitAttributes()).thenReturn( + new SplitAttributes.Builder() + .setDividerAttributes(DEFAULT_DIVIDER_ATTRIBUTES) + .build()); + final TaskFragmentContainer mockPrimaryContainer = + createMockTaskFragmentContainer( + mPrimaryContainerToken, new Rect(0, 0, 950, 1000)); + final TaskFragmentContainer mockSecondaryContainer = + createMockTaskFragmentContainer( + mSecondaryContainerToken, new Rect(1000, 0, 2000, 1000)); + when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer); + when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer); + + mProperties = new DividerPresenter.Properties( + new Configuration(), + DEFAULT_DIVIDER_ATTRIBUTES, + mSurfaceControl, + getInitialDividerPosition(mSplitContainer), + true /* isVerticalSplit */, + Display.DEFAULT_DISPLAY); + + mDividerPresenter = new DividerPresenter(); + mDividerPresenter.mProperties = mProperties; + mDividerPresenter.mRenderer = mRenderer; + mDividerPresenter.mDecorSurfaceOwner = mPrimaryContainerToken; + } + + @Test + public void testUpdateDivider() { + when(mSplitContainer.getCurrentSplitAttributes()).thenReturn( + new SplitAttributes.Builder() + .setDividerAttributes(ANOTHER_DIVIDER_ATTRIBUTES) + .build()); + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + mSplitContainer); + + assertNotEquals(mProperties, mDividerPresenter.mProperties); + verify(mRenderer).update(); + verify(mTransaction, never()).addTaskFragmentOperation(any(), any()); + } + + @Test + public void testUpdateDivider_updateDecorSurfaceOwnerIfPrimaryContainerChanged() { + final TaskFragmentContainer mockPrimaryContainer = + createMockTaskFragmentContainer( + mAnotherContainerToken, new Rect(0, 0, 750, 1000)); + final TaskFragmentContainer mockSecondaryContainer = + createMockTaskFragmentContainer( + mSecondaryContainerToken, new Rect(800, 0, 2000, 1000)); + when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer); + when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer); + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + mSplitContainer); + + assertNotEquals(mProperties, mDividerPresenter.mProperties); + verify(mRenderer).update(); + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE) + .build(); + assertEquals(mAnotherContainerToken, mDividerPresenter.mDecorSurfaceOwner); + verify(mTransaction).addTaskFragmentOperation(mAnotherContainerToken, operation); + } + + @Test + public void testUpdateDivider_noChangeIfPropertiesIdentical() { + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + mSplitContainer); + + assertEquals(mProperties, mDividerPresenter.mProperties); + verify(mRenderer, never()).update(); + verify(mTransaction, never()).addTaskFragmentOperation(any(), any()); + } + + @Test + public void testUpdateDivider_dividerRemovedWhenSplitContainerIsNull() { + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + null /* splitContainer */); + final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) + .build(); + + verify(mTransaction).addTaskFragmentOperation( + mPrimaryContainerToken, taskFragmentOperation); + verify(mRenderer).release(); + assertNull(mDividerPresenter.mRenderer); + assertNull(mDividerPresenter.mProperties); + assertNull(mDividerPresenter.mDecorSurfaceOwner); + } + + @Test + public void testUpdateDivider_dividerRemovedWhenDividerAttributesIsNull() { + when(mSplitContainer.getCurrentSplitAttributes()).thenReturn( + new SplitAttributes.Builder().setDividerAttributes(null).build()); + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + mSplitContainer); + final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) + .build(); + + verify(mTransaction).addTaskFragmentOperation( + mPrimaryContainerToken, taskFragmentOperation); + verify(mRenderer).release(); + assertNull(mDividerPresenter.mRenderer); + assertNull(mDividerPresenter.mProperties); + assertNull(mDividerPresenter.mDecorSurfaceOwner); + } + @Test public void testSanitizeDividerAttributes_setDefaultValues() { DividerAttributes attributes = @@ -61,7 +249,7 @@ public class DividerPresenterTest { public void testSanitizeDividerAttributes_notChangingValidValues() { DividerAttributes attributes = new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE) - .setWidthDp(10) + .setWidthDp(24) .setPrimaryMinRatio(0.3f) .setPrimaryMaxRatio(0.7f) .build(); @@ -123,6 +311,14 @@ public class DividerPresenterTest { dividerWidthPx, splitType, expectedTopLeftOffset, expectedBottomRightOffset); } + private TaskFragmentContainer createMockTaskFragmentContainer( + @NonNull IBinder token, @NonNull Rect bounds) { + final TaskFragmentContainer container = mock(TaskFragmentContainer.class); + when(container.getTaskFragmentToken()).thenReturn(token); + when(container.getLastRequestedBounds()).thenReturn(bounds); + return container; + } + private void assertDividerOffsetEquals( int dividerWidthPx, @NonNull SplitAttributes.SplitType splitType, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index dd087e8eb7c9..6f37e9cb794d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -107,7 +107,7 @@ public class JetpackTaskFragmentOrganizerTest { mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(mTransaction, info); - mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken()); + mOrganizer.expandTaskFragment(mTransaction, container); verify(mTransaction).setWindowingMode(container.getInfo().getToken(), WINDOWING_MODE_UNDEFINED); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index cdb37acfc0c2..c246a19f27e2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -642,7 +642,7 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); - verify(mSplitPresenter).expandTaskFragment(mTransaction, container.getTaskFragmentToken()); + verify(mSplitPresenter).expandTaskFragment(mTransaction, container); } @Test diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 941b4e1c3e41..62d8aa30a576 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -665,8 +665,8 @@ public class SplitPresenterTest { assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); - verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken()); - verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken()); + verify(mPresenter).expandTaskFragment(mTransaction, primaryTf); + verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf); splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES); clearInvocations(mPresenter); @@ -675,8 +675,8 @@ public class SplitPresenterTest { splitContainer, mActivity, null /* secondaryActivity */, new Intent(ApplicationProvider.getApplicationContext(), MinimumDimensionActivity.class))); - verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken()); - verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken()); + verify(mPresenter).expandTaskFragment(mTransaction, primaryTf); + verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf); } @Test diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 9130edfa9f26..74e85f8dd468 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -334,6 +334,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { boolean isDisplayRotationAnimationStarted = false; final boolean isDreamTransition = isDreamTransition(info); final boolean isOnlyTranslucent = isOnlyTranslucent(info); + final boolean isActivityLevel = isActivityLevelOnly(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -502,8 +503,35 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { : new Rect(change.getEndAbsBounds()); clipRect.offsetTo(0, 0); + final TransitionInfo.Root animRoot = TransitionUtil.getRootFor(change, info); + final Point animRelOffset = new Point( + change.getEndAbsBounds().left - animRoot.getOffset().x, + change.getEndAbsBounds().top - animRoot.getOffset().y); + if (change.getActivityComponent() != null && !isActivityLevel) { + // At this point, this is an independent activity change in a non-activity + // transition. This means that an activity transition got erroneously combined + // with another ongoing transition. This then means that the animation root may + // not tightly fit the activities, so we have to put them in a separate crop. + final int layer = Transitions.calculateAnimLayer(change, i, + info.getChanges().size(), info.getType()); + final SurfaceControl leash = new SurfaceControl.Builder() + .setName("Transition ActivityWrap: " + + change.getActivityComponent().toShortString()) + .setParent(animRoot.getLeash()) + .setContainerLayer().build(); + startTransaction.setCrop(leash, clipRect); + startTransaction.setPosition(leash, animRelOffset.x, animRelOffset.y); + startTransaction.setLayer(leash, layer); + startTransaction.show(leash); + startTransaction.reparent(change.getLeash(), leash); + startTransaction.setPosition(change.getLeash(), 0, 0); + animRelOffset.set(0, 0); + finishTransaction.reparent(leash, null); + leash.release(); + } + buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, - mTransactionPool, mMainExecutor, change.getEndRelOffset(), cornerRadius, + mTransactionPool, mMainExecutor, animRelOffset, cornerRadius, clipRect); if (info.getAnimationOptions() != null) { @@ -612,6 +640,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return (translucentOpen + translucentClose) > 0; } + /** + * Does `info` only contain activity-level changes? This kinda assumes that if so, they are + * all in one task. + */ + private static boolean isActivityLevelOnly(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getActivityComponent() == null) return false; + } + return true; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index ccd0b2df8cf1..6a53d33243db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -31,7 +31,6 @@ import static android.view.WindowManager.fixScale; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -530,6 +529,44 @@ public class Transitions implements RemoteCallable<Transitions>, } } + static int calculateAnimLayer(@NonNull TransitionInfo.Change change, int i, + int numChanges, @WindowManager.TransitionType int transitType) { + // Put animating stuff above this line and put static stuff below it. + final int zSplitLine = numChanges + 1; + final boolean isOpening = isOpeningType(transitType); + final boolean isClosing = isClosingType(transitType); + final int mode = change.getMode(); + // Put all the OPEN/SHOW on top + if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { + if (isOpening + // This is for when an activity launches while a different transition is + // collecting. + || change.hasFlags(FLAG_MOVED_TO_TOP)) { + // put on top + return zSplitLine + numChanges - i; + } else { + // put on bottom + return zSplitLine - i; + } + } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { + if (isOpening) { + // put on bottom and leave visible + return zSplitLine - i; + } else { + // put on top + return zSplitLine + numChanges - i; + } + } else { // CHANGE or other + if (isClosing || TransitionUtil.isOrderOnly(change)) { + // Put below CLOSE mode (in the "static" section). + return zSplitLine - i; + } else { + // Put above CLOSE mode. + return zSplitLine + numChanges - i; + } + } + } + /** * Reparents all participants into a shared parent and orders them based on: the global transit * type, their transit mode, and their destination z-order. @@ -537,19 +574,14 @@ public class Transitions implements RemoteCallable<Transitions>, private static void setupAnimHierarchy(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { final int type = info.getType(); - final boolean isOpening = isOpeningType(type); - final boolean isClosing = isClosingType(type); for (int i = 0; i < info.getRootCount(); ++i) { t.show(info.getRoot(i).getLeash()); } final int numChanges = info.getChanges().size(); - // Put animating stuff above this line and put static stuff below it. - final int zSplitLine = numChanges + 1; // changes should be ordered top-to-bottom in z for (int i = numChanges - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); final SurfaceControl leash = change.getLeash(); - final int mode = change.getMode(); // Don't reparent anything that isn't independent within its parents if (!TransitionInfo.isIndependent(change, info)) { @@ -558,50 +590,14 @@ public class Transitions implements RemoteCallable<Transitions>, boolean hasParent = change.getParent() != null; - final int rootIdx = TransitionUtil.rootIndexFor(change, info); + final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info); if (!hasParent) { - t.reparent(leash, info.getRoot(rootIdx).getLeash()); + t.reparent(leash, root.getLeash()); t.setPosition(leash, - change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x, - change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y); - } - final int layer; - // Put all the OPEN/SHOW on top - if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - // Wallpaper is always at the bottom, opening wallpaper on top of closing one. - if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { - layer = -zSplitLine + numChanges - i; - } else { - layer = -zSplitLine - i; - } - } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { - if (isOpening - // This is for when an activity launches while a different transition is - // collecting. - || change.hasFlags(FLAG_MOVED_TO_TOP)) { - // put on top - layer = zSplitLine + numChanges - i; - } else { - // put on bottom - layer = zSplitLine - i; - } - } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { - if (isOpening) { - // put on bottom and leave visible - layer = zSplitLine - i; - } else { - // put on top - layer = zSplitLine + numChanges - i; - } - } else { // CHANGE or other - if (isClosing || TransitionUtil.isOrderOnly(change)) { - // Put below CLOSE mode (in the "static" section). - layer = zSplitLine - i; - } else { - // Put above CLOSE mode. - layer = zSplitLine + numChanges - i; - } + change.getStartAbsBounds().left - root.getOffset().x, + change.getStartAbsBounds().top - root.getOffset().y); } + final int layer = calculateAnimLayer(change, i, numChanges, type); t.setLayer(leash, layer); } } diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java index be3c24806c5b..a353df743520 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -723,6 +723,7 @@ public final class ApduServiceInfo implements Parcelable { * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this * multiple times will cause the value to be overwritten each time. * @param pollingLoopFilter the polling loop filter to add, must be a valid hexadecimal string + * @param autoTransact whether Observe Mode should be disabled when this filter matches or not */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public void addPollingLoopFilter(@NonNull String pollingLoopFilter, @@ -747,6 +748,7 @@ public final class ApduServiceInfo implements Parcelable { * multiple times will cause the value to be overwritten each time. * @param pollingLoopPatternFilter the polling loop pattern filter to add, must be a valid * regex to match a hexadecimal string + * @param autoTransact whether Observe Mode should be disabled when this filter matches or not */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public void addPollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter, diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index eaec617cfa70..5629a7bf7b21 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -256,8 +256,7 @@ public class SecureSettings { Settings.Secure.HEARING_AID_MEDIA_ROUTING, Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING, Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, - Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, - Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED, + Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, Settings.Secure.HUB_MODE_TUTORIAL_STATE, Settings.Secure.STYLUS_BUTTONS_ENABLED, Settings.Secure.STYLUS_HANDWRITING_ENABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 046d6e25ff31..b8d95eb5329d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -208,8 +208,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.SEARCH_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"})); VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 02d212cb4996..dba3bac4a4b8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1950,11 +1950,8 @@ class SettingsProtoDumpUtil { Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, SecureSettingsProto.Assist.LONG_PRESS_HOME_ENABLED); dumpSetting(s, p, - Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, - SecureSettingsProto.Assist.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED); - dumpSetting(s, p, - Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED, - SecureSettingsProto.Assist.SEARCH_LONG_PRESS_HOME_ENABLED); + Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, + SecureSettingsProto.Assist.SEARCH_ALL_ENTRYPOINTS_ENABLED); dumpSetting(s, p, Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, SecureSettingsProto.Assist.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED); diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index ad09febd74a9..a155dc4d7639 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -104,6 +104,13 @@ flag { } flag { + name: "notifications_heads_up_refactor" + namespace: "systemui" + description: "Use HeadsUpInteractor to feed HUN updates to the NSSL." + bug: "325936094" +} + +flag { name: "pss_app_selector_abrupt_exit_fix" namespace: "systemui" description: "Fixes the app selector abruptly disappearing without an animation, when the" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index be63301e5749..30564bb6eb84 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -60,7 +60,7 @@ class AvalancheControllerTest : SysuiTestCase() { private val mGlobalSettings = FakeGlobalSettings() private val mSystemClock = FakeSystemClock() private val mExecutor = FakeExecutor(mSystemClock) - private var testableHeadsUpManager: BaseHeadsUpManager? = null + private lateinit var testableHeadsUpManager: BaseHeadsUpManager @Before fun setUp() { @@ -88,20 +88,15 @@ class AvalancheControllerTest : SysuiTestCase() { } private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry { - val entry = testableHeadsUpManager!!.createHeadsUpEntry() - - entry.setEntry( + return testableHeadsUpManager.createHeadsUpEntry( NotificationEntryBuilder() .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, ""))) .build() ) - return entry } private fun createFsiHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry { - val entry = testableHeadsUpManager!!.createHeadsUpEntry() - entry.setEntry(createFullScreenIntentEntry(id, mContext)) - return entry + return testableHeadsUpManager.createHeadsUpEntry(createFullScreenIntentEntry(id, mContext)) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index ed0d272cd848..3dc449514699 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -38,7 +38,6 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.app.Person; -import android.content.Intent; import android.testing.TestableLooper; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -498,16 +497,16 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() { final BaseHeadsUpManager hum = createHeadsUpManager(); - final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry(); - ongoingCall.setEntry(new NotificationEntryBuilder() - .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0, - new Notification.Builder(mContext, "") - .setCategory(Notification.CATEGORY_CALL) - .setOngoing(true))) - .build()); + final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry( + new NotificationEntryBuilder() + .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0, + new Notification.Builder(mContext, "") + .setCategory(Notification.CATEGORY_CALL) + .setOngoing(true))) + .build()); - final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(); - activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext)); + final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry( + HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext)); activeRemoteInput.mRemoteInputActive = true; assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0); @@ -518,18 +517,18 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() { final BaseHeadsUpManager hum = createHeadsUpManager(); - final BaseHeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry(); final Person person = new Person.Builder().setName("person").build(); final PendingIntent intent = mock(PendingIntent.class); - incomingCall.setEntry(new NotificationEntryBuilder() - .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0, - new Notification.Builder(mContext, "") - .setStyle(Notification.CallStyle - .forIncomingCall(person, intent, intent)))) - .build()); - - final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(); - activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext)); + final BaseHeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry( + new NotificationEntryBuilder() + .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0, + new Notification.Builder(mContext, "") + .setStyle(Notification.CallStyle + .forIncomingCall(person, intent, intent)))) + .build()); + + final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry( + HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext)); activeRemoteInput.mRemoteInputActive = true; assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0); @@ -541,8 +540,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { final BaseHeadsUpManager hum = createHeadsUpManager(); // Needs full screen intent in order to be pinned - final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(); - entryToPin.setEntry( + final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry( HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext)); // Note: the standard way to show a notification would be calling showNotification rather diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java index d8f77f054b49..3c9dc6345d31 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java @@ -54,9 +54,10 @@ class TestableHeadsUpManager extends BaseHeadsUpManager { mStickyForSomeTimeAutoDismissTime = BaseHeadsUpManagerTest.TEST_STICKY_AUTO_DISMISS_TIME; } + @NonNull @Override - protected HeadsUpEntry createHeadsUpEntry() { - mLastCreatedEntry = spy(super.createHeadsUpEntry()); + protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) { + mLastCreatedEntry = spy(super.createHeadsUpEntry(entry)); return mLastCreatedEntry; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index dfe41eb9f7f2..d49a513f6e9f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -243,7 +243,7 @@ public final class NavBarHelper implements Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), false, mAssistContentObserver, UserHandle.USER_ALL); mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Secure.SEARCH_LONG_PRESS_HOME_ENABLED), + Settings.Secure.getUriFor(Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED), false, mAssistContentObserver, UserHandle.USER_ALL); mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), @@ -443,10 +443,10 @@ public final class NavBarHelper implements boolean overrideLongPressHome = mAssistManagerLazy.get() .shouldOverrideAssist(AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); boolean longPressDefault = mContext.getResources().getBoolean(overrideLongPressHome - ? com.android.internal.R.bool.config_searchLongPressHomeEnabledDefault + ? com.android.internal.R.bool.config_searchAllEntrypointsEnabledDefault : com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, - overrideLongPressHome ? Secure.SEARCH_LONG_PRESS_HOME_ENABLED + overrideLongPressHome ? Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED : Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, mUserTracker.getUserId()) != 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 5171a5c9144c..9a82ecf01449 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -863,7 +863,7 @@ public class NotificationShelf extends ActivatableNotificationView { boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); iconState.hidden = isAppearing || (view instanceof ExpandableNotificationRow - && ((ExpandableNotificationRow) view).isLowPriority() + && ((ExpandableNotificationRow) view).isMinimized() && mShelfIcons.areIconsOverflowing()) || (transitionAmount == 0.0f && !iconState.isAnimating(icon)) || row.isAboveShelf() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index dfb0f9bb2a87..7a7b18450b48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -363,7 +363,7 @@ public class PreparationCoordinator implements Coordinator { NotifInflater.Params getInflaterParams(NotifUiAdjustment adjustment, String reason) { return new NotifInflater.Params( - /* isLowPriority = */ adjustment.isMinimized(), + /* isMinimized = */ adjustment.isMinimized(), /* reason = */ reason, /* showSnooze = */ adjustment.isSnoozeEnabled(), /* isChildInGroup = */ adjustment.isChildInGroup(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt index 7b8a062ec446..ff72888a5c26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt @@ -56,7 +56,7 @@ interface NotifInflater { /** A class holding parameters used when inflating the notification row */ class Params( - val isLowPriority: Boolean, + val isMinimized: Boolean, val reason: String, val showSnooze: Boolean, val isChildInGroup: Boolean = false, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 4bbe0357b335..4a895c0571d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -243,7 +243,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) { final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance()); - final boolean isLowPriority = inflaterParams.isLowPriority(); + final boolean isMinimized = inflaterParams.isMinimized(); // Set show snooze action row.setShowSnooze(inflaterParams.getShowSnooze()); @@ -252,7 +252,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED); params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED); params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - params.setUseLowPriority(isLowPriority); + params.setUseMinimized(isMinimized); if (screenshareNotificationHiding() ? inflaterParams.getNeedsRedaction() @@ -275,7 +275,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { if (AsyncGroupHeaderViewInflation.isEnabled()) { if (inflaterParams.isGroupSummary()) { params.requireContentViews(FLAG_GROUP_SUMMARY_HEADER); - if (isLowPriority) { + if (isMinimized) { params.requireContentViews(FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER); } } else { @@ -288,7 +288,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mRowContentBindStage.requestRebind(entry, en -> { mLogger.logRebindComplete(entry); row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setIsLowPriority(isLowPriority); + row.setIsMinimized(isMinimized); if (inflationCallback != null) { inflationCallback.onAsyncInflationFinished(en); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index c05c3c3df2c9..b8b4a03eae51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -327,7 +327,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private OnClickListener mExpandClickListener = new OnClickListener() { @Override public void onClick(View v) { - if (!shouldShowPublic() && (!mIsLowPriority || isExpanded()) + if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && mGroupMembershipManager.isGroupSummary(mEntry)) { mGroupExpansionChanging = true; final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); @@ -382,7 +382,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mAboveShelf; private OnUserInteractionCallback mOnUserInteractionCallback; private NotificationGutsManager mNotificationGutsManager; - private boolean mIsLowPriority; + private boolean mIsMinimized; private boolean mUseIncreasedCollapsedHeight; private boolean mUseIncreasedHeadsUpHeight; private float mTranslationWhenRemoved; @@ -467,7 +467,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (viewWrapper != null) { setIconAnimationRunningForChild(running, viewWrapper.getIcon()); } - NotificationViewWrapper lowPriWrapper = mChildrenContainer.getLowPriorityViewWrapper(); + NotificationViewWrapper lowPriWrapper = mChildrenContainer + .getMinimizedGroupHeaderWrapper(); if (lowPriWrapper != null) { setIconAnimationRunningForChild(running, lowPriWrapper.getIcon()); } @@ -680,7 +681,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (color != Notification.COLOR_INVALID) { return color; } else { - return mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(), + return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(), getBackgroundColorWithoutTint()); } } @@ -1545,7 +1546,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Set the low-priority group notification header view * @param headerView header view to set */ - public void setLowPriorityGroupHeader(NotificationHeaderView headerView) { + public void setMinimizedGroupHeader(NotificationHeaderView headerView) { NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull(); childrenContainer.setLowPriorityGroupHeader( /* headerViewLowPriority= */ headerView, @@ -1664,16 +1665,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public void setIsLowPriority(boolean isLowPriority) { - mIsLowPriority = isLowPriority; - mPrivateLayout.setIsLowPriority(isLowPriority); + /** + * Set if the row is minimized. + */ + public void setIsMinimized(boolean isMinimized) { + mIsMinimized = isMinimized; + mPrivateLayout.setIsLowPriority(isMinimized); if (mChildrenContainer != null) { - mChildrenContainer.setIsLowPriority(isLowPriority); + mChildrenContainer.setIsMinimized(isMinimized); } } - public boolean isLowPriority() { - return mIsLowPriority; + public boolean isMinimized() { + return mIsMinimized; } public void setUsesIncreasedCollapsedHeight(boolean use) { @@ -2050,7 +2054,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainerStub = findViewById(R.id.child_container_stub); mChildrenContainerStub.setOnInflateListener((stub, inflated) -> { mChildrenContainer = (NotificationChildrenContainer) inflated; - mChildrenContainer.setIsLowPriority(mIsLowPriority); + mChildrenContainer.setIsMinimized(mIsMinimized); mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); mChildrenContainer.onNotificationUpdated(); mChildrenContainer.setLogger(mChildrenContainerLogger); @@ -3435,7 +3439,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void onExpansionChanged(boolean userAction, boolean wasExpanded) { boolean nowExpanded = isExpanded(); - if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) { + if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) { nowExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); } if (nowExpanded != wasExpanded) { @@ -3492,7 +3496,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (!expandable) { if (mIsSummaryWithChildren) { expandable = true; - if (!mIsLowPriority || isExpanded()) { + if (!mIsMinimized || isExpanded()) { isExpanded = isGroupExpanded(); } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index f835cca1a60c..ded635cb08bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -150,7 +150,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry, mConversationProcessor, row, - bindParams.isLowPriority, + bindParams.isMinimized, bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, callback, @@ -178,7 +178,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder SmartReplyStateInflater smartRepliesInflater) { InflationProgress result = createRemoteViews(reInflateFlags, builder, - bindParams.isLowPriority, + bindParams.isMinimized, bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, packageContext, @@ -215,6 +215,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder apply( mInflationExecutor, inflateSynchronously, + bindParams.isMinimized, result, reInflateFlags, mRemoteViewCache, @@ -365,7 +366,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder } private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags, - Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight, + Notification.Builder builder, boolean isMinimized, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, Context packageContext, ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, @@ -376,13 +377,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view"); - result.newContentView = createContentView(builder, isLowPriority, + result.newContentView = createContentView(builder, isMinimized, usesIncreasedHeight); } if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view"); - result.newExpandedView = createExpandedView(builder, isLowPriority); + result.newExpandedView = createExpandedView(builder, isMinimized); } if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { @@ -393,7 +394,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating public remote view"); - result.newPublicView = builder.makePublicContentView(isLowPriority); + result.newPublicView = builder.makePublicContentView(isMinimized); } if (AsyncGroupHeaderViewInflation.isEnabled()) { @@ -406,7 +407,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating low-priority group summary remote view"); - result.mNewLowPriorityGroupHeaderView = + result.mNewMinimizedGroupHeaderView = builder.makeLowPriorityContentView(true /* useRegularSubtext */); } } @@ -444,6 +445,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private static CancellationSignal apply( Executor inflationExecutor, boolean inflateSynchronously, + boolean isMinimized, InflationProgress result, @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, @@ -475,7 +477,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } }; logger.logAsyncTaskProgress(entry, "applying contracted view"); - applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, flag, + applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, result, + reInflateFlags, flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( @@ -502,7 +505,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } }; logger.logAsyncTaskProgress(entry, "applying expanded view"); - applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, + applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, result, + reInflateFlags, flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getExpandedChild(), privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED), runningInflations, @@ -529,7 +533,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } }; logger.logAsyncTaskProgress(entry, "applying heads up view"); - applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, + applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, + result, reInflateFlags, flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getHeadsUpChild(), privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), runningInflations, @@ -555,7 +560,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } }; logger.logAsyncTaskProgress(entry, "applying public view"); - applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, flag, + applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, + result, reInflateFlags, flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, publicLayout, publicLayout.getContractedChild(), publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), @@ -583,11 +589,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder } }; logger.logAsyncTaskProgress(entry, "applying group header view"); - applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, + applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, + result, reInflateFlags, /* inflationId = */ FLAG_GROUP_SUMMARY_HEADER, remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, /* parentLayout = */ childrenContainer, - /* existingView = */ childrenContainer.getNotificationHeader(), + /* existingView = */ childrenContainer.getGroupHeader(), /* existingWrapper = */ childrenContainer.getNotificationHeaderWrapper(), runningInflations, applyCallback, logger); } @@ -595,7 +602,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) { boolean isNewView = !canReapplyRemoteView( - /* newView = */ result.mNewLowPriorityGroupHeaderView, + /* newView = */ result.mNewMinimizedGroupHeaderView, /* oldView = */ remoteViewCache.getCachedView( entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)); ApplyCallback applyCallback = new ApplyCallback() { @@ -603,29 +610,30 @@ public class NotificationContentInflater implements NotificationRowContentBinder public void setResultView(View v) { logger.logAsyncTaskProgress(entry, "low-priority group header view applied"); - result.mInflatedLowPriorityGroupHeaderView = (NotificationHeaderView) v; + result.mInflatedMinimizedGroupHeaderView = (NotificationHeaderView) v; } @Override public RemoteViews getRemoteView() { - return result.mNewLowPriorityGroupHeaderView; + return result.mNewMinimizedGroupHeaderView; } }; logger.logAsyncTaskProgress(entry, "applying low priority group header view"); - applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, + applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, + result, reInflateFlags, /* inflationId = */ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, /* parentLayout = */ childrenContainer, - /* existingView = */ childrenContainer.getNotificationHeaderLowPriority(), + /* existingView = */ childrenContainer.getMinimizedNotificationHeader(), /* existingWrapper = */ childrenContainer - .getLowPriorityViewWrapper(), + .getMinimizedGroupHeaderWrapper(), runningInflations, applyCallback, logger); } } // Let's try to finish, maybe nobody is even inflating anything - finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry, - row, logger); + finishIfDone(result, isMinimized, reInflateFlags, remoteViewCache, runningInflations, + callback, entry, row, logger); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( () -> { @@ -641,6 +649,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder static void applyRemoteView( Executor inflationExecutor, boolean inflateSynchronously, + boolean isMinimized, final InflationProgress result, final @InflationFlag int reInflateFlags, @InflationFlag int inflationId, @@ -707,7 +716,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder existingWrapper.onReinflated(); } runningInflations.remove(inflationId); - finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, + finishIfDone(result, isMinimized, + reInflateFlags, remoteViewCache, runningInflations, callback, entry, row, logger); } @@ -838,6 +848,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder * @return true if the inflation was finished */ private static boolean finishIfDone(InflationProgress result, + boolean isMinimized, @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, HashMap<Integer, CancellationSignal> runningInflations, @Nullable InflationCallback endListener, NotificationEntry entry, @@ -944,7 +955,9 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (AsyncGroupHeaderViewInflation.isEnabled()) { if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) { if (result.mInflatedGroupHeaderView != null) { - row.setIsLowPriority(false); + // We need to set if the row is minimized before setting the group header to + // make sure the setting of header view works correctly + row.setIsMinimized(isMinimized); row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView); remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER, result.mNewGroupHeaderView); @@ -957,13 +970,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder } if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) { - if (result.mInflatedLowPriorityGroupHeaderView != null) { - // New view case, set row to low priority - row.setIsLowPriority(true); - row.setLowPriorityGroupHeader( - /* headerView= */ result.mInflatedLowPriorityGroupHeaderView); + if (result.mInflatedMinimizedGroupHeaderView != null) { + // We need to set if the row is minimized before setting the group header to + // make sure the setting of header view works correctly + row.setIsMinimized(isMinimized); + row.setMinimizedGroupHeader( + /* headerView= */ result.mInflatedMinimizedGroupHeaderView); remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, - result.mNewLowPriorityGroupHeaderView); + result.mNewMinimizedGroupHeaderView); } else if (remoteViewCache.hasCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) { // Re-inflation case. Only update if it's still cached (i.e. view has not @@ -984,12 +998,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder } private static RemoteViews createExpandedView(Notification.Builder builder, - boolean isLowPriority) { + boolean isMinimized) { RemoteViews bigContentView = builder.createBigContentView(); if (bigContentView != null) { return bigContentView; } - if (isLowPriority) { + if (isMinimized) { RemoteViews contentView = builder.createContentView(); Notification.Builder.makeHeaderExpanded(contentView); return contentView; @@ -998,8 +1012,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } private static RemoteViews createContentView(Notification.Builder builder, - boolean isLowPriority, boolean useLarge) { - if (isLowPriority) { + boolean isMinimized, boolean useLarge) { + if (isMinimized) { return builder.makeLowPriorityContentView(false /* useRegularSubtext */); } return builder.createContentView(useLarge); @@ -1038,7 +1052,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotificationEntry mEntry; private final Context mContext; private final boolean mInflateSynchronously; - private final boolean mIsLowPriority; + private final boolean mIsMinimized; private final boolean mUsesIncreasedHeight; private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; @@ -1063,7 +1077,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder NotificationEntry entry, ConversationNotificationProcessor conversationProcessor, ExpandableNotificationRow row, - boolean isLowPriority, + boolean isMinimized, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, InflationCallback callback, @@ -1080,7 +1094,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteViewCache = cache; mSmartRepliesInflater = smartRepliesInflater; mContext = mRow.getContext(); - mIsLowPriority = isLowPriority; + mIsMinimized = isMinimized; mUsesIncreasedHeight = usesIncreasedHeight; mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; mRemoteViewClickHandler = remoteViewClickHandler; @@ -1150,7 +1164,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mEntry, recoveredBuilder, mLogger); } InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, - recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight, + recoveredBuilder, mIsMinimized, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext, mRow, mNotifLayoutInflaterFactoryProvider, mLogger); @@ -1209,6 +1223,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mCancellationSignal = apply( mInflationExecutor, mInflateSynchronously, + mIsMinimized, result, mReInflateFlags, mRemoteViewCache, @@ -1295,7 +1310,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private RemoteViews newExpandedView; private RemoteViews newPublicView; private RemoteViews mNewGroupHeaderView; - private RemoteViews mNewLowPriorityGroupHeaderView; + private RemoteViews mNewMinimizedGroupHeaderView; @VisibleForTesting Context packageContext; @@ -1305,7 +1320,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private View inflatedExpandedView; private View inflatedPublicView; private NotificationHeaderView mInflatedGroupHeaderView; - private NotificationHeaderView mInflatedLowPriorityGroupHeaderView; + private NotificationHeaderView mInflatedMinimizedGroupHeaderView; private CharSequence headsUpStatusBarText; private CharSequence headsUpStatusBarTextPublic; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 8a3e7e8a0580..6f00d96b6312 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1514,7 +1514,7 @@ public class NotificationContentView extends FrameLayout implements Notification } ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button); View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); - LinearLayout actionListMarginTarget = layout.findViewById( + ViewGroup actionListMarginTarget = layout.findViewById( com.android.internal.R.id.notification_action_list_margin_target); if (bubbleButton == null || actionContainer == null) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index b0fd47587782..33339a7fe025 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -128,9 +128,9 @@ public interface NotificationRowContentBinder { class BindParams { /** - * Bind a low priority version of the content views. + * Bind a minimized version of the content views. */ - public boolean isLowPriority; + public boolean isMinimized; /** * Use increased height when binding contracted view. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 1494c275d061..bae89fbf626f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -26,7 +26,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin * Parameters for {@link RowContentBindStage}. */ public final class RowContentBindParams { - private boolean mUseLowPriority; + private boolean mUseMinimized; private boolean mUseIncreasedHeight; private boolean mUseIncreasedHeadsUpHeight; private boolean mViewsNeedReinflation; @@ -41,17 +41,20 @@ public final class RowContentBindParams { private @InflationFlag int mDirtyContentViews = mContentViews; /** - * Set whether content should use a low priority version of its content views. + * Set whether content should use a minimized version of its content views. */ - public void setUseLowPriority(boolean useLowPriority) { - if (mUseLowPriority != useLowPriority) { + public void setUseMinimized(boolean useMinimized) { + if (mUseMinimized != useMinimized) { mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED); } - mUseLowPriority = useLowPriority; + mUseMinimized = useMinimized; } - public boolean useLowPriority() { - return mUseLowPriority; + /** + * @return Whether the row uses the minimized style. + */ + public boolean useMinimized() { + return mUseMinimized; } /** @@ -149,9 +152,9 @@ public final class RowContentBindParams { @Override public String toString() { return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x " - + "mUseLowPriority=%b mUseIncreasedHeight=%b " + + "mUseMinimized=%b mUseIncreasedHeight=%b " + "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]", - mContentViews, mDirtyContentViews, mUseLowPriority, mUseIncreasedHeight, + mContentViews, mDirtyContentViews, mUseMinimized, mUseIncreasedHeight, mUseIncreasedHeadsUpHeight, mViewsNeedReinflation); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java index f4f8374d0a9f..89fcda949b5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -73,7 +73,7 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> { mBinder.unbindContent(entry, row, contentToUnbind); BindParams bindParams = new BindParams(); - bindParams.isLowPriority = params.useLowPriority(); + bindParams.isMinimized = params.useMinimized(); bindParams.usesIncreasedHeight = params.useIncreasedHeight(); bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight(); boolean forceInflate = params.needsReinflation(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt new file mode 100644 index 000000000000..62641fe2f229 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the notifications heads up refactor flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationsHeadsUpRefactor { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationsHeadsUpRefactor() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 28f874da0c74..5dc37e0525da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -110,14 +110,14 @@ public class NotificationChildrenContainer extends ViewGroup */ private boolean mEnableShadowOnChildNotifications; - private NotificationHeaderView mNotificationHeader; - private NotificationHeaderViewWrapper mNotificationHeaderWrapper; - private NotificationHeaderView mNotificationHeaderLowPriority; - private NotificationHeaderViewWrapper mNotificationHeaderWrapperLowPriority; + private NotificationHeaderView mGroupHeader; + private NotificationHeaderViewWrapper mGroupHeaderWrapper; + private NotificationHeaderView mMinimizedGroupHeader; + private NotificationHeaderViewWrapper mMinimizedGroupHeaderWrapper; private NotificationGroupingUtil mGroupingUtil; private ViewState mHeaderViewState; private int mClipBottomAmount; - private boolean mIsLowPriority; + private boolean mIsMinimized; private OnClickListener mHeaderClickListener; private ViewGroup mCurrentHeader; private boolean mIsConversation; @@ -217,14 +217,14 @@ public class NotificationChildrenContainer extends ViewGroup int right = left + mOverflowNumber.getMeasuredWidth(); mOverflowNumber.layout(left, 0, right, mOverflowNumber.getMeasuredHeight()); } - if (mNotificationHeader != null) { - mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(), - mNotificationHeader.getMeasuredHeight()); + if (mGroupHeader != null) { + mGroupHeader.layout(0, 0, mGroupHeader.getMeasuredWidth(), + mGroupHeader.getMeasuredHeight()); } - if (mNotificationHeaderLowPriority != null) { - mNotificationHeaderLowPriority.layout(0, 0, - mNotificationHeaderLowPriority.getMeasuredWidth(), - mNotificationHeaderLowPriority.getMeasuredHeight()); + if (mMinimizedGroupHeader != null) { + mMinimizedGroupHeader.layout(0, 0, + mMinimizedGroupHeader.getMeasuredWidth(), + mMinimizedGroupHeader.getMeasuredHeight()); } } @@ -271,11 +271,11 @@ public class NotificationChildrenContainer extends ViewGroup } int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); - if (mNotificationHeader != null) { - mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec); + if (mGroupHeader != null) { + mGroupHeader.measure(widthMeasureSpec, headerHeightSpec); } - if (mNotificationHeaderLowPriority != null) { - mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec); + if (mMinimizedGroupHeader != null) { + mMinimizedGroupHeader.measure(widthMeasureSpec, headerHeightSpec); } setMeasuredDimension(width, height); @@ -308,11 +308,11 @@ public class NotificationChildrenContainer extends ViewGroup * appropriately. */ public void setNotificationGroupWhen(long whenMillis) { - if (mNotificationHeaderWrapper != null) { - mNotificationHeaderWrapper.setNotificationWhen(whenMillis); + if (mGroupHeaderWrapper != null) { + mGroupHeaderWrapper.setNotificationWhen(whenMillis); } - if (mNotificationHeaderWrapperLowPriority != null) { - mNotificationHeaderWrapperLowPriority.setNotificationWhen(whenMillis); + if (mMinimizedGroupHeaderWrapper != null) { + mMinimizedGroupHeaderWrapper.setNotificationWhen(whenMillis); } } @@ -410,28 +410,28 @@ public class NotificationChildrenContainer extends ViewGroup Trace.beginSection("recreateHeader#makeNotificationGroupHeader"); RemoteViews header = builder.makeNotificationGroupHeader(); Trace.endSection(); - if (mNotificationHeader == null) { + if (mGroupHeader == null) { Trace.beginSection("recreateHeader#apply"); - mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this); + mGroupHeader = (NotificationHeaderView) header.apply(getContext(), this); Trace.endSection(); - mNotificationHeader.findViewById(com.android.internal.R.id.expand_button) + mGroupHeader.findViewById(com.android.internal.R.id.expand_button) .setVisibility(VISIBLE); - mNotificationHeader.setOnClickListener(mHeaderClickListener); - mNotificationHeaderWrapper = + mGroupHeader.setOnClickListener(mHeaderClickListener); + mGroupHeaderWrapper = (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap( getContext(), - mNotificationHeader, + mGroupHeader, mContainingNotification); - mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate); - addView(mNotificationHeader, 0); + mGroupHeaderWrapper.setOnRoundnessChangedListener(this::invalidate); + addView(mGroupHeader, 0); invalidate(); } else { Trace.beginSection("recreateHeader#reapply"); - header.reapply(getContext(), mNotificationHeader); + header.reapply(getContext(), mGroupHeader); Trace.endSection(); } - mNotificationHeaderWrapper.setExpanded(mChildrenExpanded); - mNotificationHeaderWrapper.onContentUpdated(mContainingNotification); + mGroupHeaderWrapper.setExpanded(mChildrenExpanded); + mGroupHeaderWrapper.onContentUpdated(mContainingNotification); recreateLowPriorityHeader(builder, isConversation); updateHeaderVisibility(false /* animate */); updateChildrenAppearance(); @@ -439,21 +439,21 @@ public class NotificationChildrenContainer extends ViewGroup } private void removeGroupHeader() { - if (mNotificationHeader == null) { + if (mGroupHeader == null) { return; } - removeView(mNotificationHeader); - mNotificationHeader = null; - mNotificationHeaderWrapper = null; + removeView(mGroupHeader); + mGroupHeader = null; + mGroupHeaderWrapper = null; } private void removeLowPriorityGroupHeader() { - if (mNotificationHeaderLowPriority == null) { + if (mMinimizedGroupHeader == null) { return; } - removeView(mNotificationHeaderLowPriority); - mNotificationHeaderLowPriority = null; - mNotificationHeaderWrapperLowPriority = null; + removeView(mMinimizedGroupHeader); + mMinimizedGroupHeader = null; + mMinimizedGroupHeaderWrapper = null; } /** @@ -474,21 +474,21 @@ public class NotificationChildrenContainer extends ViewGroup return; } - mNotificationHeader = headerView; - mNotificationHeader.findViewById(com.android.internal.R.id.expand_button) + mGroupHeader = headerView; + mGroupHeader.findViewById(com.android.internal.R.id.expand_button) .setVisibility(VISIBLE); - mNotificationHeader.setOnClickListener(mHeaderClickListener); - mNotificationHeaderWrapper = + mGroupHeader.setOnClickListener(mHeaderClickListener); + mGroupHeaderWrapper = (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap( getContext(), - mNotificationHeader, + mGroupHeader, mContainingNotification); - mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate); - addView(mNotificationHeader, 0); + mGroupHeaderWrapper.setOnRoundnessChangedListener(this::invalidate); + addView(mGroupHeader, 0); invalidate(); - mNotificationHeaderWrapper.setExpanded(mChildrenExpanded); - mNotificationHeaderWrapper.onContentUpdated(mContainingNotification); + mGroupHeaderWrapper.setExpanded(mChildrenExpanded); + mGroupHeaderWrapper.onContentUpdated(mContainingNotification); updateHeaderVisibility(false /* animate */); updateChildrenAppearance(); @@ -511,20 +511,20 @@ public class NotificationChildrenContainer extends ViewGroup return; } - mNotificationHeaderLowPriority = headerViewLowPriority; - mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button) + mMinimizedGroupHeader = headerViewLowPriority; + mMinimizedGroupHeader.findViewById(com.android.internal.R.id.expand_button) .setVisibility(VISIBLE); - mNotificationHeaderLowPriority.setOnClickListener(onClickListener); - mNotificationHeaderWrapperLowPriority = + mMinimizedGroupHeader.setOnClickListener(onClickListener); + mMinimizedGroupHeaderWrapper = (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap( getContext(), - mNotificationHeaderLowPriority, + mMinimizedGroupHeader, mContainingNotification); - mNotificationHeaderWrapperLowPriority.setOnRoundnessChangedListener(this::invalidate); - addView(mNotificationHeaderLowPriority, 0); + mMinimizedGroupHeaderWrapper.setOnRoundnessChangedListener(this::invalidate); + addView(mMinimizedGroupHeader, 0); invalidate(); - mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification); + mMinimizedGroupHeaderWrapper.onContentUpdated(mContainingNotification); updateHeaderVisibility(false /* animate */); updateChildrenAppearance(); } @@ -539,35 +539,35 @@ public class NotificationChildrenContainer extends ViewGroup AsyncGroupHeaderViewInflation.assertInLegacyMode(); RemoteViews header; StatusBarNotification notification = mContainingNotification.getEntry().getSbn(); - if (mIsLowPriority) { + if (mIsMinimized) { if (builder == null) { builder = Notification.Builder.recoverBuilder(getContext(), notification.getNotification()); } header = builder.makeLowPriorityContentView(true /* useRegularSubtext */); - if (mNotificationHeaderLowPriority == null) { - mNotificationHeaderLowPriority = (NotificationHeaderView) header.apply(getContext(), + if (mMinimizedGroupHeader == null) { + mMinimizedGroupHeader = (NotificationHeaderView) header.apply(getContext(), this); - mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button) + mMinimizedGroupHeader.findViewById(com.android.internal.R.id.expand_button) .setVisibility(VISIBLE); - mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener); - mNotificationHeaderWrapperLowPriority = + mMinimizedGroupHeader.setOnClickListener(mHeaderClickListener); + mMinimizedGroupHeaderWrapper = (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap( getContext(), - mNotificationHeaderLowPriority, + mMinimizedGroupHeader, mContainingNotification); - mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate); - addView(mNotificationHeaderLowPriority, 0); + mGroupHeaderWrapper.setOnRoundnessChangedListener(this::invalidate); + addView(mMinimizedGroupHeader, 0); invalidate(); } else { - header.reapply(getContext(), mNotificationHeaderLowPriority); + header.reapply(getContext(), mMinimizedGroupHeader); } - mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification); - resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader()); + mMinimizedGroupHeaderWrapper.onContentUpdated(mContainingNotification); + resetHeaderVisibilityIfNeeded(mMinimizedGroupHeader, calculateDesiredHeader()); } else { - removeView(mNotificationHeaderLowPriority); - mNotificationHeaderLowPriority = null; - mNotificationHeaderWrapperLowPriority = null; + removeView(mMinimizedGroupHeader); + mMinimizedGroupHeader = null; + mMinimizedGroupHeaderWrapper = null; } } @@ -588,8 +588,8 @@ public class NotificationChildrenContainer extends ViewGroup public void updateGroupOverflow() { if (mShowGroupCountInExpander) { - setExpandButtonNumber(mNotificationHeaderWrapper); - setExpandButtonNumber(mNotificationHeaderWrapperLowPriority); + setExpandButtonNumber(mGroupHeaderWrapper); + setExpandButtonNumber(mMinimizedGroupHeaderWrapper); return; } int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); @@ -641,9 +641,9 @@ public class NotificationChildrenContainer extends ViewGroup * @param alpha alpha value to apply to the content */ public void setContentAlpha(float alpha) { - if (mNotificationHeader != null) { - for (int i = 0; i < mNotificationHeader.getChildCount(); i++) { - mNotificationHeader.getChildAt(i).setAlpha(alpha); + if (mGroupHeader != null) { + for (int i = 0; i < mGroupHeader.getChildCount(); i++) { + mGroupHeader.getChildAt(i).setAlpha(alpha); } } for (ExpandableNotificationRow child : getAttachedChildren()) { @@ -683,7 +683,7 @@ public class NotificationChildrenContainer extends ViewGroup if (AsyncGroupHeaderViewInflation.isEnabled()) { return mHeaderHeight; } else { - return mNotificationHeaderLowPriority.getHeight(); + return mMinimizedGroupHeader.getHeight(); } } int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; @@ -837,15 +837,15 @@ public class NotificationChildrenContainer extends ViewGroup mGroupOverFlowState.setAlpha(0.0f); } } - if (mNotificationHeader != null) { + if (mGroupHeader != null) { if (mHeaderViewState == null) { mHeaderViewState = new ViewState(); } - mHeaderViewState.initFrom(mNotificationHeader); + mHeaderViewState.initFrom(mGroupHeader); if (mContainingNotification.hasExpandingChild()) { // Not modifying translationZ during expand animation. - mHeaderViewState.setZTranslation(mNotificationHeader.getTranslationZ()); + mHeaderViewState.setZTranslation(mGroupHeader.getTranslationZ()); } else if (childrenExpandedAndNotAnimating) { mHeaderViewState.setZTranslation(parentState.getZTranslation()); } else { @@ -898,7 +898,7 @@ public class NotificationChildrenContainer extends ViewGroup && !showingAsLowPriority()) { return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; } - if (mIsLowPriority + if (mIsMinimized || (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded()) || (mContainingNotification.isHeadsUpState() && mContainingNotification.canShowHeadsUp())) { @@ -946,7 +946,7 @@ public class NotificationChildrenContainer extends ViewGroup mNeverAppliedGroupState = false; } if (mHeaderViewState != null) { - mHeaderViewState.applyToView(mNotificationHeader); + mHeaderViewState.applyToView(mGroupHeader); } updateChildrenClipping(); } @@ -1006,8 +1006,8 @@ public class NotificationChildrenContainer extends ViewGroup } if (child instanceof NotificationHeaderView - && mNotificationHeaderWrapper.hasRoundedCorner()) { - float[] radii = mNotificationHeaderWrapper.getUpdatedRadii(); + && mGroupHeaderWrapper.hasRoundedCorner()) { + float[] radii = mGroupHeaderWrapper.getUpdatedRadii(); mHeaderPath.reset(); mHeaderPath.addRoundRect( child.getLeft(), @@ -1085,8 +1085,8 @@ public class NotificationChildrenContainer extends ViewGroup } mGroupOverFlowState.animateTo(mOverflowNumber, properties); } - if (mNotificationHeader != null) { - mHeaderViewState.applyToView(mNotificationHeader); + if (mGroupHeader != null) { + mHeaderViewState.applyToView(mGroupHeader); } updateChildrenClipping(); } @@ -1109,8 +1109,8 @@ public class NotificationChildrenContainer extends ViewGroup public void setChildrenExpanded(boolean childrenExpanded) { mChildrenExpanded = childrenExpanded; updateExpansionStates(); - if (mNotificationHeaderWrapper != null) { - mNotificationHeaderWrapper.setExpanded(childrenExpanded); + if (mGroupHeaderWrapper != null) { + mGroupHeaderWrapper.setExpanded(childrenExpanded); } final int count = mAttachedChildren.size(); for (int childIdx = 0; childIdx < count; childIdx++) { @@ -1130,11 +1130,11 @@ public class NotificationChildrenContainer extends ViewGroup } public NotificationViewWrapper getNotificationViewWrapper() { - return mNotificationHeaderWrapper; + return mGroupHeaderWrapper; } - public NotificationViewWrapper getLowPriorityViewWrapper() { - return mNotificationHeaderWrapperLowPriority; + public NotificationViewWrapper getMinimizedGroupHeaderWrapper() { + return mMinimizedGroupHeaderWrapper; } @VisibleForTesting @@ -1142,12 +1142,12 @@ public class NotificationChildrenContainer extends ViewGroup return mCurrentHeader; } - public NotificationHeaderView getNotificationHeader() { - return mNotificationHeader; + public NotificationHeaderView getGroupHeader() { + return mGroupHeader; } - public NotificationHeaderView getNotificationHeaderLowPriority() { - return mNotificationHeaderLowPriority; + public NotificationHeaderView getMinimizedNotificationHeader() { + return mMinimizedGroupHeader; } private void updateHeaderVisibility(boolean animate) { @@ -1171,7 +1171,7 @@ public class NotificationChildrenContainer extends ViewGroup NotificationViewWrapper hiddenWrapper = getWrapperForView(currentHeader); visibleWrapper.transformFrom(hiddenWrapper); hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false)); - startChildAlphaAnimations(desiredHeader == mNotificationHeader); + startChildAlphaAnimations(desiredHeader == mGroupHeader); } else { animate = false; } @@ -1192,8 +1192,8 @@ public class NotificationChildrenContainer extends ViewGroup } } - resetHeaderVisibilityIfNeeded(mNotificationHeader, desiredHeader); - resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, desiredHeader); + resetHeaderVisibilityIfNeeded(mGroupHeader, desiredHeader); + resetHeaderVisibilityIfNeeded(mMinimizedGroupHeader, desiredHeader); mCurrentHeader = desiredHeader; } @@ -1215,9 +1215,9 @@ public class NotificationChildrenContainer extends ViewGroup private ViewGroup calculateDesiredHeader() { ViewGroup desiredHeader; if (showingAsLowPriority()) { - desiredHeader = mNotificationHeaderLowPriority; + desiredHeader = mMinimizedGroupHeader; } else { - desiredHeader = mNotificationHeader; + desiredHeader = mGroupHeader; } return desiredHeader; } @@ -1244,20 +1244,20 @@ public class NotificationChildrenContainer extends ViewGroup private void updateHeaderTransformation() { if (mUserLocked && showingAsLowPriority()) { float fraction = getGroupExpandFraction(); - mNotificationHeaderWrapper.transformFrom(mNotificationHeaderWrapperLowPriority, + mGroupHeaderWrapper.transformFrom(mMinimizedGroupHeaderWrapper, fraction); - mNotificationHeader.setVisibility(VISIBLE); - mNotificationHeaderWrapperLowPriority.transformTo(mNotificationHeaderWrapper, + mGroupHeader.setVisibility(VISIBLE); + mMinimizedGroupHeaderWrapper.transformTo(mGroupHeaderWrapper, fraction); } } private NotificationViewWrapper getWrapperForView(View visibleHeader) { - if (visibleHeader == mNotificationHeader) { - return mNotificationHeaderWrapper; + if (visibleHeader == mGroupHeader) { + return mGroupHeaderWrapper; } - return mNotificationHeaderWrapperLowPriority; + return mMinimizedGroupHeaderWrapper; } /** @@ -1266,13 +1266,13 @@ public class NotificationChildrenContainer extends ViewGroup * @param expanded whether the group is expanded. */ public void updateHeaderForExpansion(boolean expanded) { - if (mNotificationHeader != null) { + if (mGroupHeader != null) { if (expanded) { ColorDrawable cd = new ColorDrawable(); cd.setColor(mContainingNotification.calculateBgColor()); - mNotificationHeader.setHeaderBackgroundDrawable(cd); + mGroupHeader.setHeaderBackgroundDrawable(cd); } else { - mNotificationHeader.setHeaderBackgroundDrawable(null); + mGroupHeader.setHeaderBackgroundDrawable(null); } } } @@ -1405,11 +1405,11 @@ public class NotificationChildrenContainer extends ViewGroup if (AsyncGroupHeaderViewInflation.isEnabled()) { return mHeaderHeight; } - if (mNotificationHeaderLowPriority == null) { + if (mMinimizedGroupHeader == null) { Log.e(TAG, "getMinHeight: low priority header is null", new Exception()); return 0; } - return mNotificationHeaderLowPriority.getHeight(); + return mMinimizedGroupHeader.getHeight(); } int minExpandHeight = mNotificationHeaderMargin + headerTranslation; int visibleChildren = 0; @@ -1443,20 +1443,20 @@ public class NotificationChildrenContainer extends ViewGroup } public boolean showingAsLowPriority() { - return mIsLowPriority && !mContainingNotification.isExpanded(); + return mIsMinimized && !mContainingNotification.isExpanded(); } public void reInflateViews(OnClickListener listener, StatusBarNotification notification) { if (!AsyncGroupHeaderViewInflation.isEnabled()) { // When Async header inflation is enabled, we do not reinflate headers because they are // inflated from the background thread - if (mNotificationHeader != null) { - removeView(mNotificationHeader); - mNotificationHeader = null; + if (mGroupHeader != null) { + removeView(mGroupHeader); + mGroupHeader = null; } - if (mNotificationHeaderLowPriority != null) { - removeView(mNotificationHeaderLowPriority); - mNotificationHeaderLowPriority = null; + if (mMinimizedGroupHeader != null) { + removeView(mMinimizedGroupHeader); + mMinimizedGroupHeader = null; } recreateNotificationHeader(listener, mIsConversation); } @@ -1489,8 +1489,8 @@ public class NotificationChildrenContainer extends ViewGroup } private void updateHeaderTouchability() { - if (mNotificationHeader != null) { - mNotificationHeader.setAcceptAllTouches(mChildrenExpanded || mUserLocked); + if (mGroupHeader != null) { + mGroupHeader.setAcceptAllTouches(mChildrenExpanded || mUserLocked); } } @@ -1534,8 +1534,11 @@ public class NotificationChildrenContainer extends ViewGroup updateChildrenClipping(); } - public void setIsLowPriority(boolean isLowPriority) { - mIsLowPriority = isLowPriority; + /** + * Set whether the children container is minimized. + */ + public void setIsMinimized(boolean isMinimized) { + mIsMinimized = isMinimized; if (mContainingNotification != null) { /* we're not yet set up yet otherwise */ if (!AsyncGroupHeaderViewInflation.isEnabled()) { recreateLowPriorityHeader(null /* existingBuilder */, mIsConversation); @@ -1552,13 +1555,13 @@ public class NotificationChildrenContainer extends ViewGroup */ public NotificationViewWrapper getVisibleWrapper() { if (showingAsLowPriority()) { - return mNotificationHeaderWrapperLowPriority; + return mMinimizedGroupHeaderWrapper; } - return mNotificationHeaderWrapper; + return mGroupHeaderWrapper; } public void onExpansionChanged() { - if (mIsLowPriority) { + if (mIsMinimized) { if (mUserLocked) { setUserLocked(mUserLocked); } @@ -1574,15 +1577,15 @@ public class NotificationChildrenContainer extends ViewGroup @Override public void applyRoundnessAndInvalidate() { boolean last = true; - if (mNotificationHeaderWrapper != null) { - mNotificationHeaderWrapper.requestTopRoundness( + if (mGroupHeaderWrapper != null) { + mGroupHeaderWrapper.requestTopRoundness( /* value = */ getTopRoundness(), /* sourceType = */ FROM_PARENT, /* animate = */ false ); } - if (mNotificationHeaderWrapperLowPriority != null) { - mNotificationHeaderWrapperLowPriority.requestTopRoundness( + if (mMinimizedGroupHeaderWrapper != null) { + mMinimizedGroupHeaderWrapper.requestTopRoundness( /* value = */ getTopRoundness(), /* sourceType = */ FROM_PARENT, /* animate = */ false @@ -1612,31 +1615,31 @@ public class NotificationChildrenContainer extends ViewGroup * Shows the given feedback icon, or hides the icon if null. */ public void setFeedbackIcon(@Nullable FeedbackIcon icon) { - if (mNotificationHeaderWrapper != null) { - mNotificationHeaderWrapper.setFeedbackIcon(icon); + if (mGroupHeaderWrapper != null) { + mGroupHeaderWrapper.setFeedbackIcon(icon); } - if (mNotificationHeaderWrapperLowPriority != null) { - mNotificationHeaderWrapperLowPriority.setFeedbackIcon(icon); + if (mMinimizedGroupHeaderWrapper != null) { + mMinimizedGroupHeaderWrapper.setFeedbackIcon(icon); } } public void setRecentlyAudiblyAlerted(boolean audiblyAlertedRecently) { - if (mNotificationHeaderWrapper != null) { - mNotificationHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + if (mGroupHeaderWrapper != null) { + mGroupHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently); } - if (mNotificationHeaderWrapperLowPriority != null) { - mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + if (mMinimizedGroupHeaderWrapper != null) { + mMinimizedGroupHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently); } } @Override public void setNotificationFaded(boolean faded) { mContainingNotificationIsFaded = faded; - if (mNotificationHeaderWrapper != null) { - mNotificationHeaderWrapper.setNotificationFaded(faded); + if (mGroupHeaderWrapper != null) { + mGroupHeaderWrapper.setNotificationFaded(faded); } - if (mNotificationHeaderWrapperLowPriority != null) { - mNotificationHeaderWrapperLowPriority.setNotificationFaded(faded); + if (mMinimizedGroupHeaderWrapper != null) { + mMinimizedGroupHeaderWrapper.setNotificationFaded(faded); } for (ExpandableNotificationRow child : mAttachedChildren) { child.setNotificationFaded(faded); @@ -1654,7 +1657,7 @@ public class NotificationChildrenContainer extends ViewGroup } public NotificationHeaderViewWrapper getNotificationHeaderWrapper() { - return mNotificationHeaderWrapper; + return mGroupHeaderWrapper; } public void setLogger(NotificationChildrenContainerLogger logger) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 24be3db6231f..86bb844e7be3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.collection.provider.OnReorder import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.AnimationStateHandler; import com.android.systemui.statusbar.policy.AvalancheController; @@ -94,6 +95,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Override public HeadsUpEntryPhone acquire() { + NotificationsHeadsUpRefactor.assertInLegacyMode(); if (!mPoolObjects.isEmpty()) { return mPoolObjects.pop(); } @@ -102,6 +104,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Override public boolean release(@NonNull HeadsUpEntryPhone instance) { + NotificationsHeadsUpRefactor.assertInLegacyMode(); mPoolObjects.push(instance); return true; } @@ -371,15 +374,24 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp /////////////////////////////////////////////////////////////////////////////////////////////// // HeadsUpManager utility (protected) methods overrides: + @NonNull @Override - protected HeadsUpEntry createHeadsUpEntry() { - return mEntryPool.acquire(); + protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) { + if (NotificationsHeadsUpRefactor.isEnabled()) { + return new HeadsUpEntryPhone(entry); + } else { + HeadsUpEntryPhone headsUpEntry = mEntryPool.acquire(); + headsUpEntry.setEntry(entry); + return headsUpEntry; + } } @Override protected void onEntryRemoved(HeadsUpEntry headsUpEntry) { super.onEntryRemoved(headsUpEntry); - mEntryPool.release((HeadsUpEntryPhone) headsUpEntry); + if (!NotificationsHeadsUpRefactor.isEnabled()) { + mEntryPool.release((HeadsUpEntryPhone) headsUpEntry); + } } @Override @@ -439,14 +451,22 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp */ private boolean extended; - @Override public boolean isSticky() { return super.isSticky() || mGutsShownPinned; } - public void setEntry(@NonNull final NotificationEntry entry) { - Runnable removeHeadsUpRunnable = () -> { + public HeadsUpEntryPhone() { + super(); + } + + public HeadsUpEntryPhone(NotificationEntry entry) { + super(entry); + } + + @Override + protected Runnable createRemoveRunnable(NotificationEntry entry) { + return () -> { if (!mVisualStabilityProvider.isReorderingAllowed() // We don't want to allow reordering while pulsing, but headsup need to // time out anyway @@ -460,8 +480,6 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp removeEntry(entry.getKey()); } }; - - setEntry(entry, removeHeadsUpRunnable); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 50de3cba6b59..6f7e0468c246 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -39,6 +39,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; @@ -162,11 +163,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @Override public void showNotification(@NonNull NotificationEntry entry) { - HeadsUpEntry headsUpEntry = createHeadsUpEntry(); - - // Attach NotificationEntry for AvalancheController to log key and - // record mPostTime for AvalancheController sorting - headsUpEntry.setEntry(entry); + HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry); Runnable runnable = () -> { // TODO(b/315362456) log outside runnable too @@ -375,7 +372,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } /** - * Remove a notification and reset the entry. + * Remove a notification from the alerting entries. * @param key key of notification to remove */ protected final void removeEntry(@NonNull String key) { @@ -395,7 +392,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { mHeadsUpEntryMap.remove(key); onEntryRemoved(headsUpEntry); entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - headsUpEntry.reset(); + if (NotificationsHeadsUpRefactor.isEnabled()) { + headsUpEntry.cancelAutoRemovalCallbacks("removeEntry"); + } else { + headsUpEntry.reset(); + } }; mAvalancheController.delete(headsUpEntry, runnable, "removeEntry"); } @@ -657,8 +658,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } @NonNull - protected HeadsUpEntry createHeadsUpEntry() { - return new HeadsUpEntry(); + protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) { + return new HeadsUpEntry(entry); } /** @@ -694,11 +695,23 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @Nullable private Runnable mCancelRemoveRunnable; + public HeadsUpEntry() { + NotificationsHeadsUpRefactor.assertInLegacyMode(); + } + + public HeadsUpEntry(NotificationEntry entry) { + // Attach NotificationEntry for AvalancheController to log key and + // record mPostTime for AvalancheController sorting + setEntry(entry, createRemoveRunnable(entry)); + } + + /** Attach a NotificationEntry. */ public void setEntry(@NonNull final NotificationEntry entry) { - setEntry(entry, () -> removeEntry(entry.getKey())); + NotificationsHeadsUpRefactor.assertInLegacyMode(); + setEntry(entry, createRemoveRunnable(entry)); } - public void setEntry(@NonNull final NotificationEntry entry, + private void setEntry(@NonNull final NotificationEntry entry, @Nullable Runnable removeRunnable) { mEntry = entry; mRemoveRunnable = removeRunnable; @@ -847,6 +860,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } public void reset() { + NotificationsHeadsUpRefactor.assertInLegacyMode(); cancelAutoRemovalCallbacks("reset()"); mEntry = null; mRemoveRunnable = null; @@ -919,6 +933,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } } + /** Creates a runnable to remove this notification from the alerting entries. */ + protected Runnable createRemoveRunnable(NotificationEntry entry) { + return () -> removeEntry(entry.getKey()); + } + /** * Calculate what the post time of a notification is at some current time. * @return the post time diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index 419b0fd2f89b..118d27a68c8c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -251,7 +251,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any()); - assertFalse(mParamsCaptor.getValue().isLowPriority()); + assertFalse(mParamsCaptor.getValue().isMinimized()); mNotifInflater.invokeInflateCallbackForEntry(mEntry); // WHEN notification moves to a min priority section @@ -260,7 +260,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // THEN we rebind it verify(mNotifInflater).rebindViews(eq(mEntry), mParamsCaptor.capture(), any()); - assertTrue(mParamsCaptor.getValue().isLowPriority()); + assertTrue(mParamsCaptor.getValue().isMinimized()); // THEN we do not filter it because it's not the first inflation. assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); @@ -273,7 +273,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any()); - assertTrue(mParamsCaptor.getValue().isLowPriority()); + assertTrue(mParamsCaptor.getValue().isMinimized()); mNotifInflater.invokeInflateCallbackForEntry(mEntry); // WHEN notification is moved under a parent @@ -282,7 +282,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // THEN we rebind it as not-minimized verify(mNotifInflater).rebindViews(eq(mEntry), mParamsCaptor.capture(), any()); - assertFalse(mParamsCaptor.getValue().isLowPriority()); + assertFalse(mParamsCaptor.getValue().isMinimized()); // THEN we do not filter it because it's not the first inflation. assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index b114e13bb25c..ee2eb806341f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -741,7 +741,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { when(mockViewWrapper.getIcon()).thenReturn(mockIcon); NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class); - when(mockContainer.getLowPriorityViewWrapper()).thenReturn(mockLowPriorityViewWrapper); + when(mockContainer.getMinimizedGroupHeaderWrapper()).thenReturn(mockLowPriorityViewWrapper); CachingIconView mockLowPriorityIcon = mock(CachingIconView.class); when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index a0d10759ba56..8c225113677b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -231,6 +231,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { NotificationContentInflater.applyRemoteView( AsyncTask.SERIAL_EXECUTOR, false /* inflateSynchronously */, + /* isMinimized= */ false, result, FLAG_CONTENT_VIEW_EXPANDED, 0, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index 76470dbe6d21..1534c84fd99a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -197,7 +197,7 @@ public class RowContentBindStageTest extends SysuiTestCase { params.clearDirtyContentViews(); // WHEN low priority is set and stage executed. - params.setUseLowPriority(true); + params.setUseMinimized(true); mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); // THEN binder is called with use low priority and contracted/expanded are called to bind. @@ -210,7 +210,7 @@ public class RowContentBindStageTest extends SysuiTestCase { anyBoolean(), any()); BindParams usedParams = bindParamsCaptor.getValue(); - assertTrue(usedParams.isLowPriority); + assertTrue(usedParams.isMinimized); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index 1f38a73020b2..3b16f1416935 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -67,7 +67,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test public void testGetMaxAllowedVisibleChildren_lowPriority() { - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(), NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED); } @@ -81,7 +81,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test public void testGetMaxAllowedVisibleChildren_lowPriority_expandedChildren() { - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); mChildrenContainer.setChildrenExpanded(true); Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(), NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED); @@ -89,7 +89,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test public void testGetMaxAllowedVisibleChildren_lowPriority_userLocked() { - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); mChildrenContainer.setUserLocked(true); Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(), NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED); @@ -118,7 +118,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test public void testShowingAsLowPriority_lowPriority() { - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); Assert.assertTrue(mChildrenContainer.showingAsLowPriority()); } @@ -129,7 +129,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test public void testShowingAsLowPriority_lowPriority_expanded() { - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); mGroup.setExpandable(true); mGroup.setUserExpanded(true, false); Assert.assertFalse(mChildrenContainer.showingAsLowPriority()); @@ -140,7 +140,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { mGroup.setUserLocked(true); mGroup.setExpandable(true); mGroup.setUserExpanded(true); - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(), NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); } @@ -148,14 +148,14 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) public void testLowPriorityHeaderCleared() { - mGroup.setIsLowPriority(true); + mGroup.setIsMinimized(true); NotificationHeaderView lowPriorityHeaderView = - mChildrenContainer.getLowPriorityViewWrapper().getNotificationHeader(); + mChildrenContainer.getMinimizedGroupHeaderWrapper().getNotificationHeader(); Assert.assertEquals(View.VISIBLE, lowPriorityHeaderView.getVisibility()); Assert.assertSame(mChildrenContainer, lowPriorityHeaderView.getParent()); - mGroup.setIsLowPriority(false); + mGroup.setIsMinimized(false); assertNull(lowPriorityHeaderView.getParent()); - assertNull(mChildrenContainer.getLowPriorityViewWrapper()); + assertNull(mChildrenContainer.getMinimizedGroupHeaderWrapper()); } @Test @@ -169,7 +169,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test @EnableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) public void testSetLowPriorityWithAsyncInflation_noHeaderReInflation() { - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); assertNull("We don't inflate header from the main thread with Async " + "Inflation enabled", mChildrenContainer.getCurrentHeaderView()); } @@ -179,21 +179,21 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { public void setLowPriorityBeforeLowPriorityHeaderSet() { //Given: the children container does not have a low-priority header, and is not low-priority - assertNull(mChildrenContainer.getLowPriorityViewWrapper()); - mGroup.setIsLowPriority(false); + assertNull(mChildrenContainer.getMinimizedGroupHeaderWrapper()); + mGroup.setIsMinimized(false); //When: set the children container to be low-priority and set the low-priority header - mGroup.setIsLowPriority(true); - mGroup.setLowPriorityGroupHeader(createHeaderView(/* lowPriorityHeader= */ true)); + mGroup.setIsMinimized(true); + mGroup.setMinimizedGroupHeader(createHeaderView(/* lowPriorityHeader= */ true)); //Then: the low-priority group header should be visible NotificationHeaderView lowPriorityHeaderView = - mChildrenContainer.getLowPriorityViewWrapper().getNotificationHeader(); + mChildrenContainer.getMinimizedGroupHeaderWrapper().getNotificationHeader(); Assert.assertEquals(View.VISIBLE, lowPriorityHeaderView.getVisibility()); Assert.assertSame(mChildrenContainer, lowPriorityHeaderView.getParent()); //When: set the children container to be not low-priority and set the normal header - mGroup.setIsLowPriority(false); + mGroup.setIsMinimized(false); mGroup.setGroupHeader(createHeaderView(/* lowPriorityHeader= */ false)); //Then: the low-priority group header should not be visible , normal header should be @@ -211,9 +211,9 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { public void changeLowPriorityAfterHeaderSet() { //Given: the children container does not have headers, and is not low-priority - assertNull(mChildrenContainer.getLowPriorityViewWrapper()); + assertNull(mChildrenContainer.getMinimizedGroupHeaderWrapper()); assertNull(mChildrenContainer.getNotificationHeaderWrapper()); - mGroup.setIsLowPriority(false); + mGroup.setIsMinimized(false); //When: set the set the normal header mGroup.setGroupHeader(createHeaderView(/* lowPriorityHeader= */ false)); @@ -225,14 +225,14 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { Assert.assertSame(mChildrenContainer, headerView.getParent()); //When: set the set the row to be low priority, and set the low-priority header - mGroup.setIsLowPriority(true); - mGroup.setLowPriorityGroupHeader(createHeaderView(/* lowPriorityHeader= */ true)); + mGroup.setIsMinimized(true); + mGroup.setMinimizedGroupHeader(createHeaderView(/* lowPriorityHeader= */ true)); //Then: the header view should not be visible, the low-priority group header should be // visible Assert.assertEquals(View.INVISIBLE, headerView.getVisibility()); NotificationHeaderView lowPriorityHeaderView = - mChildrenContainer.getLowPriorityViewWrapper().getNotificationHeader(); + mChildrenContainer.getMinimizedGroupHeaderWrapper().getNotificationHeader(); Assert.assertEquals(View.VISIBLE, lowPriorityHeaderView.getVisibility()); } @@ -263,7 +263,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Test @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_headerLowPriority() { - mChildrenContainer.setIsLowPriority(true); + mChildrenContainer.setIsMinimized(true); NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper(); Assert.assertEquals(0f, header.getTopRoundness(), 0.001f); diff --git a/services/core/Android.bp b/services/core/Android.bp index d1d7ee7ba0e4..7f5867fb1a74 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -242,6 +242,7 @@ java_library_static { "apache-commons-math", "backstage_power_flags_lib", "notification_flags_lib", + "power_hint_flags_lib", "biometrics_flags_lib", "am_flags_lib", "com_android_server_accessibility_flags_lib", diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index b7ece2ea65b1..5905b7de5b6e 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -366,7 +366,6 @@ public class Vpn { private PendingIntent mStatusIntent; private volatile boolean mEnableTeardown = true; - private final INetworkManagementService mNms; private final INetd mNetd; @VisibleForTesting @GuardedBy("this") @@ -626,7 +625,6 @@ public class Vpn { mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); mDeps = deps; - mNms = netService; mNetd = netd; mUserId = userId; mLooper = looper; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 12a589264c28..f655455c5a6b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -530,6 +530,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { // TODO(b/178103325): Track sleep/requested sleep for every display. volatile boolean mRequestedOrSleepingDefaultDisplay; + /** + * This is used to check whether to invoke {@link #updateScreenOffSleepToken} when screen is + * turned off. E.g. if it is false when screen is turned off and the display is swapping, it + * is expected that the screen will be on in a short time. Then it is unnecessary to acquire + * screen-off-sleep-token, so it can avoid intermediate visibility or lifecycle changes. + */ + volatile boolean mIsGoingToSleepDefaultDisplay; + volatile boolean mRecentsVisible; volatile boolean mNavBarVirtualKeyHapticFeedbackEnabled = true; volatile boolean mPictureInPictureVisible; @@ -5470,6 +5478,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { } mRequestedOrSleepingDefaultDisplay = true; + mIsGoingToSleepDefaultDisplay = true; + + // In case startedGoingToSleep is called after screenTurnedOff (the source caller is in + // order but the methods run on different threads) and updateScreenOffSleepToken was + // skipped. Then acquire sleep token if screen was off. + if (!mDefaultDisplayPolicy.isScreenOnFully() && !mDefaultDisplayPolicy.isScreenOnEarly() + && com.android.window.flags.Flags.skipSleepingWhenSwitchingDisplay()) { + updateScreenOffSleepToken(true /* acquire */, false /* isSwappingDisplay */); + } if (mKeyguardDelegate != null) { mKeyguardDelegate.onStartedGoingToSleep(pmSleepReason); @@ -5493,6 +5510,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { MetricsLogger.histogram(mContext, "screen_timeout", mLockScreenTimeout / 1000); mRequestedOrSleepingDefaultDisplay = false; + mIsGoingToSleepDefaultDisplay = false; mDefaultDisplayPolicy.setAwake(false); // We must get this work done here because the power manager will drop @@ -5528,7 +5546,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } EventLogTags.writeScreenToggled(1); - + mIsGoingToSleepDefaultDisplay = false; mDefaultDisplayPolicy.setAwake(true); // Since goToSleep performs these functions synchronously, we must @@ -5630,7 +5648,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off..."); if (displayId == DEFAULT_DISPLAY) { - updateScreenOffSleepToken(true, isSwappingDisplay); + if (!isSwappingDisplay || mIsGoingToSleepDefaultDisplay + || !com.android.window.flags.Flags.skipSleepingWhenSwitchingDisplay()) { + updateScreenOffSleepToken(true /* acquire */, isSwappingDisplay); + } mRequestedOrSleepingDefaultDisplay = false; mDefaultDisplayPolicy.screenTurnedOff(); synchronized (mLock) { diff --git a/services/core/java/com/android/server/power/hint/Android.bp b/services/core/java/com/android/server/power/hint/Android.bp new file mode 100644 index 000000000000..8a98de673c3d --- /dev/null +++ b/services/core/java/com/android/server/power/hint/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "power_hint_flags", + package: "com.android.server.power.hint", + srcs: [ + "flags.aconfig", + ], +} + +java_aconfig_library { + name: "power_hint_flags_lib", + aconfig_declarations: "power_hint_flags", +} diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index aa1a41eee220..3f1b1c1e99df 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -17,6 +17,7 @@ package com.android.server.power.hint; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import static com.android.server.power.hint.Flags.powerhintThreadCleanup; import android.annotation.NonNull; import android.app.ActivityManager; @@ -26,9 +27,12 @@ import android.app.UidObserver; import android.content.Context; import android.hardware.power.WorkDuration; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.IHintManager; import android.os.IHintSession; +import android.os.Looper; +import android.os.Message; import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; @@ -36,6 +40,8 @@ import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.IntArray; +import android.util.Slog; import android.util.SparseIntArray; import android.util.StatsEvent; @@ -46,20 +52,31 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; /** An hint service implementation that runs in System Server process. */ public final class HintManagerService extends SystemService { private static final String TAG = "HintManagerService"; private static final boolean DEBUG = false; + + private static final int EVENT_CLEAN_UP_UID = 3; + @VisibleForTesting static final int CLEAN_UP_UID_DELAY_MILLIS = 1000; + + @VisibleForTesting final long mHintSessionPreferredRate; // Multi-level map storing all active AppHintSessions. @@ -73,9 +90,15 @@ public final class HintManagerService extends SystemService { /** Lock to protect HAL handles and listen list. */ private final Object mLock = new Object(); + @GuardedBy("mNonIsolatedTidsLock") + private final Map<Integer, Set<Long>> mNonIsolatedTids; + + private final Object mNonIsolatedTidsLock = new Object(); + @VisibleForTesting final MyUidObserver mUidObserver; private final NativeWrapper mNativeWrapper; + private final CleanUpHandler mCleanUpHandler; private final ActivityManagerInternal mAmInternal; @@ -94,6 +117,13 @@ public final class HintManagerService extends SystemService { HintManagerService(Context context, Injector injector) { super(context); mContext = context; + if (powerhintThreadCleanup()) { + mCleanUpHandler = new CleanUpHandler(createCleanUpThread().getLooper()); + mNonIsolatedTids = new HashMap<>(); + } else { + mCleanUpHandler = null; + mNonIsolatedTids = null; + } mActiveSessions = new ArrayMap<>(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper.halInit(); @@ -103,6 +133,13 @@ public final class HintManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class)); } + private ServiceThread createCleanUpThread() { + final ServiceThread handlerThread = new ServiceThread(TAG, + Process.THREAD_PRIORITY_LOWEST, true /*allowIo*/); + handlerThread.start(); + return handlerThread; + } + @VisibleForTesting static class Injector { NativeWrapper createNativeWrapper() { @@ -306,7 +343,18 @@ public final class HintManagerService extends SystemService { public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { FgThread.getHandler().post(() -> { synchronized (mCacheLock) { - mProcStatesCache.put(uid, procState); + if (powerhintThreadCleanup()) { + final boolean before = isUidForeground(uid); + mProcStatesCache.put(uid, procState); + final boolean after = isUidForeground(uid); + if (before != after) { + final Message msg = mCleanUpHandler.obtainMessage(EVENT_CLEAN_UP_UID, + uid); + mCleanUpHandler.sendMessageDelayed(msg, CLEAN_UP_UID_DELAY_MILLIS); + } + } else { + mProcStatesCache.put(uid, procState); + } } boolean shouldAllowUpdate = isUidForeground(uid); synchronized (mLock) { @@ -314,9 +362,10 @@ public final class HintManagerService extends SystemService { if (tokenMap == null) { return; } - for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) { - for (AppHintSession s : sessionSet) { - s.onProcStateChanged(shouldAllowUpdate); + for (int i = tokenMap.size() - 1; i >= 0; i--) { + final ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i); + for (int j = sessionSet.size() - 1; j >= 0; j--) { + sessionSet.valueAt(j).onProcStateChanged(shouldAllowUpdate); } } } @@ -324,52 +373,237 @@ public final class HintManagerService extends SystemService { } } + final class CleanUpHandler extends Handler { + // status of processed tid used for caching + private static final int TID_NOT_CHECKED = 0; + private static final int TID_PASSED_CHECK = 1; + private static final int TID_EXITED = 2; + + CleanUpHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == EVENT_CLEAN_UP_UID) { + if (hasEqualMessages(msg.what, msg.obj)) { + removeEqualMessages(msg.what, msg.obj); + final Message newMsg = obtainMessage(msg.what, msg.obj); + sendMessageDelayed(newMsg, CLEAN_UP_UID_DELAY_MILLIS); + return; + } + final int uid = (int) msg.obj; + boolean isForeground = mUidObserver.isUidForeground(uid); + // store all sessions in a list and release the global lock + // we don't need to worry about stale data or racing as the session is synchronized + // itself and will perform its own closed status check in setThreads call + final List<AppHintSession> sessions; + synchronized (mLock) { + final ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = + mActiveSessions.get(uid); + if (tokenMap == null || tokenMap.isEmpty()) { + return; + } + sessions = new ArrayList<>(tokenMap.size()); + for (int i = tokenMap.size() - 1; i >= 0; i--) { + final ArraySet<AppHintSession> set = tokenMap.valueAt(i); + for (int j = set.size() - 1; j >= 0; j--) { + sessions.add(set.valueAt(j)); + } + } + } + final long[] durationList = new long[sessions.size()]; + final int[] invalidTidCntList = new int[sessions.size()]; + final SparseIntArray checkedTids = new SparseIntArray(); + int[] totalTidCnt = new int[1]; + for (int i = sessions.size() - 1; i >= 0; i--) { + final AppHintSession session = sessions.get(i); + final long start = System.nanoTime(); + try { + final int invalidCnt = cleanUpSession(session, checkedTids, totalTidCnt); + final long elapsed = System.nanoTime() - start; + invalidTidCntList[i] = invalidCnt; + durationList[i] = elapsed; + } catch (Exception e) { + Slog.e(TAG, "Failed to clean up session " + session.mHalSessionPtr + + " for UID " + session.mUid); + } + } + logCleanUpMetrics(uid, invalidTidCntList, durationList, sessions.size(), + totalTidCnt[0], isForeground); + } + } + + private void logCleanUpMetrics(int uid, int[] count, long[] durationNsList, int sessionCnt, + int totalTidCnt, boolean isForeground) { + int maxInvalidTidCnt = Integer.MIN_VALUE; + int totalInvalidTidCnt = 0; + for (int i = 0; i < count.length; i++) { + totalInvalidTidCnt += count[i]; + maxInvalidTidCnt = Math.max(maxInvalidTidCnt, count[i]); + } + if (DEBUG || totalInvalidTidCnt > 0) { + Arrays.sort(durationNsList); + long totalDurationNs = 0; + for (int i = 0; i < durationNsList.length; i++) { + totalDurationNs += durationNsList[i]; + } + int totalDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(totalDurationNs); + int maxDurationUs = (int) TimeUnit.NANOSECONDS.toMicros( + durationNsList[durationNsList.length - 1]); + int minDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(durationNsList[0]); + int avgDurationUs = (int) TimeUnit.NANOSECONDS.toMicros( + totalDurationNs / durationNsList.length); + int th90DurationUs = (int) TimeUnit.NANOSECONDS.toMicros( + durationNsList[(int) (durationNsList.length * 0.9)]); + Slog.d(TAG, + "Invalid tid found for UID" + uid + " in " + totalDurationUs + "us:\n\t" + + "count(" + + " session: " + sessionCnt + + " totalTid: " + totalTidCnt + + " maxInvalidTid: " + maxInvalidTidCnt + + " totalInvalidTid: " + totalInvalidTidCnt + ")\n\t" + + "time per session(" + + " min: " + minDurationUs + "us" + + " max: " + maxDurationUs + "us" + + " avg: " + avgDurationUs + "us" + + " 90%: " + th90DurationUs + "us" + ")\n\t" + + "isForeground: " + isForeground); + } + } + + // This will check if each TID currently linked to the session still exists. If it's + // previously registered as not an isolated process, then it will run tkill(pid, tid, 0) to + // verify that it's still running under the same pid. Otherwise, it will run + // kill(tid, 0) to only check if it exists. The result will be cached in checkedTids + // map with tid as the key and checked status as value. + public int cleanUpSession(AppHintSession session, SparseIntArray checkedTids, int[] total) { + if (session.isClosed()) { + return 0; + } + final int pid = session.mPid; + final int[] tids = session.getTidsInternal(); + if (total != null && total.length == 1) { + total[0] += tids.length; + } + final IntArray filtered = new IntArray(tids.length); + for (int i = 0; i < tids.length; i++) { + int tid = tids[i]; + if (checkedTids.get(tid, 0) != TID_NOT_CHECKED) { + if (checkedTids.get(tid) == TID_PASSED_CHECK) { + filtered.add(tid); + } + continue; + } + // if it was registered as a non-isolated then we perform more restricted check + final boolean isNotIsolated; + synchronized (mNonIsolatedTidsLock) { + isNotIsolated = mNonIsolatedTids.containsKey(tid); + } + try { + if (isNotIsolated) { + Process.checkTid(pid, tid); + } else { + Process.checkPid(tid); + } + checkedTids.put(tid, TID_PASSED_CHECK); + filtered.add(tid); + } catch (NoSuchElementException e) { + checkedTids.put(tid, TID_EXITED); + } catch (Exception e) { + Slog.w(TAG, "Unexpected exception when checking TID " + tid + " under PID " + + pid + "(isolated: " + !isNotIsolated + ")", e); + // if anything unexpected happens then we keep it, but don't store it as checked + filtered.add(tid); + } + } + final int diff = tids.length - filtered.size(); + if (diff > 0) { + synchronized (session) { + // in case thread list is updated during the cleanup then we skip updating + // the session but just return the number for reporting purpose + final int[] newTids = session.getTidsInternal(); + if (newTids.length != tids.length) { + Slog.d(TAG, "Skipped cleaning up the session as new tids are added"); + return diff; + } + Arrays.sort(newTids); + Arrays.sort(tids); + if (!Arrays.equals(newTids, tids)) { + Slog.d(TAG, "Skipped cleaning up the session as new tids are updated"); + return diff; + } + Slog.d(TAG, "Cleaned up " + diff + " invalid tids for session " + + session.mHalSessionPtr + " with UID " + session.mUid + "\n\t" + + "before: " + Arrays.toString(tids) + "\n\t" + + "after: " + filtered); + final int[] filteredTids = filtered.toArray(); + if (filteredTids.length == 0) { + session.mShouldForcePause = true; + if (session.mUpdateAllowed) { + session.pause(); + } + } else { + session.setThreadsInternal(filteredTids, false); + } + } + } + return diff; + } + } + @VisibleForTesting IHintManager.Stub getBinderServiceInstance() { return mService; } // returns the first invalid tid or null if not found - private Integer checkTidValid(int uid, int tgid, int [] tids) { + private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) { // Make sure all tids belongs to the same UID (including isolated UID), // tids can belong to different application processes. List<Integer> isolatedPids = null; - for (int threadId : tids) { + for (int i = 0; i < tids.length; i++) { + int tid = tids[i]; final String[] procStatusKeys = new String[] { "Uid:", "Tgid:" }; long[] output = new long[procStatusKeys.length]; - Process.readProcLines("/proc/" + threadId + "/status", procStatusKeys, output); + Process.readProcLines("/proc/" + tid + "/status", procStatusKeys, output); int uidOfThreadId = (int) output[0]; int pidOfThreadId = (int) output[1]; - // use PID check for isolated processes, use UID check for non-isolated processes. - if (pidOfThreadId == tgid || uidOfThreadId == uid) { + // use PID check for non-isolated processes + if (nonIsolated != null && pidOfThreadId == tgid) { + nonIsolated.add(tid); + continue; + } + // use UID check for isolated processes. + if (uidOfThreadId == uid) { continue; } // Only call into AM if the tid is either isolated or invalid if (isolatedPids == null) { // To avoid deadlock, do not call into AMS if the call is from system. if (uid == Process.SYSTEM_UID) { - return threadId; + return tid; } isolatedPids = mAmInternal.getIsolatedProcesses(uid); if (isolatedPids == null) { - return threadId; + return tid; } } if (isolatedPids.contains(pidOfThreadId)) { continue; } - return threadId; + return tid; } return null; } private String formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid) { return "Tid" + invalidTid + " from list " + Arrays.toString(tids) - + " doesn't belong to the calling application" + callingUid; + + " doesn't belong to the calling application " + callingUid; } @VisibleForTesting @@ -387,7 +621,10 @@ public final class HintManagerService extends SystemService { final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); try { - final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids); + final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray(tids.length) + : null; + final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids, + nonIsolated); if (invalidTid != null) { final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid); Slogf.w(TAG, errMsg); @@ -396,6 +633,14 @@ public final class HintManagerService extends SystemService { long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid, tids, durationNanos); + if (powerhintThreadCleanup()) { + synchronized (mNonIsolatedTidsLock) { + for (int i = nonIsolated.size() - 1; i >= 0; i--) { + mNonIsolatedTids.putIfAbsent(nonIsolated.get(i), new ArraySet<>()); + mNonIsolatedTids.get(nonIsolated.get(i)).add(halSessionPtr); + } + } + } if (halSessionPtr == 0) { return null; } @@ -482,6 +727,7 @@ public final class HintManagerService extends SystemService { protected boolean mUpdateAllowed; protected int[] mNewThreadIds; protected boolean mPowerEfficient; + protected boolean mShouldForcePause; private enum SessionModes { POWER_EFFICIENCY, @@ -498,6 +744,7 @@ public final class HintManagerService extends SystemService { mTargetDurationNanos = durationNanos; mUpdateAllowed = true; mPowerEfficient = false; + mShouldForcePause = false; final boolean allowed = mUidObserver.isUidForeground(mUid); updateHintAllowed(allowed); try { @@ -511,7 +758,7 @@ public final class HintManagerService extends SystemService { @VisibleForTesting boolean updateHintAllowed(boolean allowed) { synchronized (this) { - if (allowed && !mUpdateAllowed) resume(); + if (allowed && !mUpdateAllowed && !mShouldForcePause) resume(); if (!allowed && mUpdateAllowed) pause(); mUpdateAllowed = allowed; return mUpdateAllowed; @@ -521,7 +768,7 @@ public final class HintManagerService extends SystemService { @Override public void updateTargetWorkDuration(long targetDurationNanos) { synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { + if (mHalSessionPtr == 0 || !mUpdateAllowed || mShouldForcePause) { return; } Preconditions.checkArgument(targetDurationNanos > 0, "Expected" @@ -534,7 +781,7 @@ public final class HintManagerService extends SystemService { @Override public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) { synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { + if (mHalSessionPtr == 0 || !mUpdateAllowed || mShouldForcePause) { return; } Preconditions.checkArgument(actualDurationNanos.length != 0, "the count" @@ -581,12 +828,25 @@ public final class HintManagerService extends SystemService { if (sessionSet.isEmpty()) tokenMap.remove(mToken); if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); } + if (powerhintThreadCleanup()) { + synchronized (mNonIsolatedTidsLock) { + final int[] tids = getTidsInternal(); + for (int tid : tids) { + if (mNonIsolatedTids.containsKey(tid)) { + mNonIsolatedTids.get(tid).remove(mHalSessionPtr); + if (mNonIsolatedTids.get(tid).isEmpty()) { + mNonIsolatedTids.remove(tid); + } + } + } + } + } } @Override public void sendHint(@PerformanceHintManager.Session.Hint int hint) { synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { + if (mHalSessionPtr == 0 || !mUpdateAllowed || mShouldForcePause) { return; } Preconditions.checkArgument(hint >= 0, "the hint ID value should be" @@ -596,33 +856,60 @@ public final class HintManagerService extends SystemService { } public void setThreads(@NonNull int[] tids) { + setThreadsInternal(tids, true); + } + + private void setThreadsInternal(int[] tids, boolean checkTid) { + if (tids.length == 0) { + throw new IllegalArgumentException("Thread id list can't be empty."); + } + synchronized (this) { if (mHalSessionPtr == 0) { return; } - if (tids.length == 0) { - throw new IllegalArgumentException("Thread id list can't be empty."); - } - final int callingUid = Binder.getCallingUid(); - final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); - final long identity = Binder.clearCallingIdentity(); - try { - final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids); - if (invalidTid != null) { - final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid); - Slogf.w(TAG, errMsg); - throw new SecurityException(errMsg); - } - } finally { - Binder.restoreCallingIdentity(identity); - } if (!mUpdateAllowed) { Slogf.v(TAG, "update hint not allowed, storing tids."); mNewThreadIds = tids; + mShouldForcePause = false; return; } + if (checkTid) { + final int callingUid = Binder.getCallingUid(); + final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); + final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray() : null; + final long identity = Binder.clearCallingIdentity(); + try { + final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids, + nonIsolated); + if (invalidTid != null) { + final String errMsg = formatTidCheckErrMsg(callingUid, tids, + invalidTid); + Slogf.w(TAG, errMsg); + throw new SecurityException(errMsg); + } + if (powerhintThreadCleanup()) { + synchronized (mNonIsolatedTidsLock) { + for (int i = nonIsolated.size() - 1; i >= 0; i--) { + mNonIsolatedTids.putIfAbsent(nonIsolated.get(i), + new ArraySet<>()); + mNonIsolatedTids.get(nonIsolated.get(i)).add(mHalSessionPtr); + } + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } mNativeWrapper.halSetThreads(mHalSessionPtr, tids); mThreadIds = tids; + mNewThreadIds = null; + // if the update is allowed but the session is force paused by tid clean up, then + // it's waiting for this tid update to resume + if (mShouldForcePause) { + resume(); + mShouldForcePause = false; + } } } @@ -632,10 +919,24 @@ public final class HintManagerService extends SystemService { } } + @VisibleForTesting + int[] getTidsInternal() { + synchronized (this) { + return mNewThreadIds != null ? Arrays.copyOf(mNewThreadIds, mNewThreadIds.length) + : Arrays.copyOf(mThreadIds, mThreadIds.length); + } + } + + boolean isClosed() { + synchronized (this) { + return mHalSessionPtr == 0; + } + } + @Override public void setMode(int mode, boolean enabled) { synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { + if (mHalSessionPtr == 0 || !mUpdateAllowed || mShouldForcePause) { return; } Preconditions.checkArgument(mode >= 0, "the mode Id value should be" @@ -650,13 +951,13 @@ public final class HintManagerService extends SystemService { @Override public void reportActualWorkDuration2(WorkDuration[] workDurations) { synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { + if (mHalSessionPtr == 0 || !mUpdateAllowed || mShouldForcePause) { return; } Preconditions.checkArgument(workDurations.length != 0, "the count" + " of work durations shouldn't be 0."); - for (WorkDuration workDuration : workDurations) { - validateWorkDuration(workDuration); + for (int i = 0; i < workDurations.length; i++) { + validateWorkDuration(workDurations[i]); } mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations); } @@ -743,6 +1044,7 @@ public final class HintManagerService extends SystemService { pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds)); pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos); pw.println(prefix + "SessionAllowed: " + mUpdateAllowed); + pw.println(prefix + "SessionForcePaused: " + mShouldForcePause); pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false")); } } diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig new file mode 100644 index 000000000000..f4afcb141b19 --- /dev/null +++ b/services/core/java/com/android/server/power/hint/flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.power.hint" + +flag { + name: "powerhint_thread_cleanup" + namespace: "game" + description: "Feature flag for auto PowerHintSession dead thread cleanup" + bug: "296160319" +} diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 30134d815fa6..e157318543f6 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -283,14 +283,14 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { int lastSyncSeqId, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, - Bundle outSyncSeqIdBundle) { + Bundle outBundle) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); int res = mService.relayoutWindow(this, window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq, lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState, - outActiveControls, outSyncSeqIdBundle); + outActiveControls, outBundle); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 4c282bd1cb65..18d2718437a6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -6822,8 +6822,8 @@ class Task extends TaskFragment { * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The * decor surface is created and shared with the client app with - * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and - * be removed with + * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE} + * and be removed with * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}. * * When boosted with diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 2934574acc03..60848a787c95 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -304,6 +304,7 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; import android.view.inputmethod.ImeTracker; +import android.window.ActivityWindowInfo; import android.window.AddToSurfaceSyncGroupResult; import android.window.ClientWindowFrames; import android.window.IGlobalDragListener; @@ -2213,7 +2214,7 @@ public class WindowManagerService extends IWindowManager.Stub int lastSyncSeqId, ClientWindowFrames outFrames, MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, - Bundle outSyncIdBundle) { + Bundle outBundle) { if (outActiveControls != null) { outActiveControls.set(null); } @@ -2544,6 +2545,13 @@ public class WindowManagerService extends IWindowManager.Stub if (outFrames != null && outMergedConfiguration != null) { win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration, false /* useLatestConfig */, shouldRelayout); + if (Flags.activityWindowInfoFlag() && outBundle != null + && win.mActivityRecord != null) { + final ActivityWindowInfo activityWindowInfo = win.mActivityRecord + .getActivityWindowInfo(); + outBundle.putParcelable(IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, + activityWindowInfo); + } // Set resize-handled here because the values are sent back to the client. win.onResizeHandled(); @@ -2573,7 +2581,7 @@ public class WindowManagerService extends IWindowManager.Stub win.isVisible() /* visible */, false /* removed */); } - if (outSyncIdBundle != null) { + if (outBundle != null) { final int maybeSyncSeqId; if (win.syncNextBuffer() && viewVisibility == View.VISIBLE && win.mSyncSeqId > lastSyncSeqId) { @@ -2582,7 +2590,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { maybeSyncSeqId = -1; } - outSyncIdBundle.putInt("seqid", maybeSyncSeqId); + outBundle.putInt(IWindowSession.KEY_RELAYOUT_BUNDLE_SEQID, maybeSyncSeqId); } if (configChanged) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index d967cde84cbf..14ec41f072dd 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -23,7 +23,7 @@ import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; -import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; @@ -1558,7 +1558,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } break; } - case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: { + case OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE: { taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment); break; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3b2a3dd9763a..e202bbf022bc 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1230,10 +1230,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(ThermalManagerService.class); t.traceEnd(); - t.traceBegin("StartHintManager"); - mSystemServiceManager.startService(HintManagerService.class); - t.traceEnd(); - // Now that the power manager has been started, let the activity manager // initialize power management features. t.traceBegin("InitPowerManagement"); @@ -1614,6 +1610,10 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } + t.traceBegin("StartHintManager"); + mSystemServiceManager.startService(HintManagerService.class); + t.traceEnd(); + // Grants default permissions and defines roles t.traceBegin("StartRoleManagerService"); LocalManagerRegistry.addManager(RoleServicePlatformHelper.class, diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index 66599e9e9125..510e7c42f12d 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -17,6 +17,8 @@ package com.android.server.power.hint; +import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -45,6 +47,9 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Log; import com.android.server.FgThread; @@ -54,11 +59,13 @@ import com.android.server.power.hint.HintManagerService.Injector; import com.android.server.power.hint.HintManagerService.NativeWrapper; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -71,7 +78,7 @@ import java.util.concurrent.locks.LockSupport; * Tests for {@link com.android.server.power.hint.HintManagerService}. * * Build/Install/Run: - * atest FrameworksServicesTests:HintManagerServiceTest + * atest FrameworksServicesTests:HintManagerServiceTest */ public class HintManagerServiceTest { private static final String TAG = "HintManagerServiceTest"; @@ -110,9 +117,15 @@ public class HintManagerServiceTest { makeWorkDuration(2L, 13L, 2L, 8L, 0L), }; - @Mock private Context mContext; - @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; - @Mock private ActivityManagerInternal mAmInternalMock; + @Mock + private Context mContext; + @Mock + private HintManagerService.NativeWrapper mNativeWrapperMock; + @Mock + private ActivityManagerInternal mAmInternalMock; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); private HintManagerService mService; @@ -122,12 +135,11 @@ public class HintManagerServiceTest { when(mNativeWrapperMock.halGetHintSessionPreferredRate()) .thenReturn(DEFAULT_HINT_PREFERRED_RATE); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), - eq(DEFAULT_TARGET_DURATION))).thenReturn(1L); + eq(DEFAULT_TARGET_DURATION))).thenReturn(1L); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B), - eq(DEFAULT_TARGET_DURATION))).thenReturn(2L); + eq(DEFAULT_TARGET_DURATION))).thenReturn(2L); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C), - eq(0L))).thenReturn(1L); - when(mAmInternalMock.getIsolatedProcesses(anyInt())).thenReturn(null); + eq(0L))).thenReturn(1L); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); } @@ -434,6 +446,163 @@ public class HintManagerServiceTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_POWERHINT_THREAD_CLEANUP) + public void testCleanupDeadThreads() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + CountDownLatch stopLatch1 = new CountDownLatch(1); + int threadCount = 3; + int[] tids1 = createThreads(threadCount, stopLatch1); + long sessionPtr1 = 111; + when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(tids1), + eq(DEFAULT_TARGET_DURATION))).thenReturn(sessionPtr1); + AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, tids1, DEFAULT_TARGET_DURATION); + assertNotNull(session1); + + // for test only to avoid conflicting with any real thread that exists on device + int isoProc1 = -100; + int isoProc2 = 9999; + when(mAmInternalMock.getIsolatedProcesses(eq(UID))).thenReturn(List.of(0)); + + CountDownLatch stopLatch2 = new CountDownLatch(1); + int[] tids2 = createThreads(threadCount, stopLatch2); + int[] tids2WithIsolated = Arrays.copyOf(tids2, tids2.length + 2); + int[] expectedTids2 = Arrays.copyOf(tids2, tids2.length + 1); + expectedTids2[tids2.length] = isoProc1; + tids2WithIsolated[threadCount] = isoProc1; + tids2WithIsolated[threadCount + 1] = isoProc2; + long sessionPtr2 = 222; + when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(tids2WithIsolated), + eq(DEFAULT_TARGET_DURATION))).thenReturn(sessionPtr2); + AppHintSession session2 = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, tids2WithIsolated, DEFAULT_TARGET_DURATION); + assertNotNull(session2); + + // trigger clean up through UID state change by making the process background + service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500) + TimeUnit.MILLISECONDS.toNanos( + CLEAN_UP_UID_DELAY_MILLIS)); + verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); + verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any()); + // the new TIDs pending list should be updated + assertArrayEquals(session2.getTidsInternal(), expectedTids2); + reset(mNativeWrapperMock); + + // this should resume and update the threads so those never-existed invalid isolated + // processes should be cleaned up + service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); + // wait for the async uid state change to trigger resume and setThreads + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500)); + verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr2), eq(expectedTids2)); + reset(mNativeWrapperMock); + + // let all session 1 threads to exit and the cleanup should force pause the session + stopLatch1.countDown(); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); + service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500) + TimeUnit.MILLISECONDS.toNanos( + CLEAN_UP_UID_DELAY_MILLIS)); + verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1)); + verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); + verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any()); + // all hints will have no effect as the session is force paused while proc in foreground + verifyAllHintsEnabled(session1, false); + verifyAllHintsEnabled(session2, true); + reset(mNativeWrapperMock); + + // in foreground, set new tids for session 1 then it should be resumed and all hints allowed + stopLatch1 = new CountDownLatch(1); + tids1 = createThreads(threadCount, stopLatch1); + session1.setThreads(tids1); + verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1), eq(tids1)); + verify(mNativeWrapperMock, times(1)).halResumeHintSession(eq(sessionPtr1)); + verifyAllHintsEnabled(session1, true); + reset(mNativeWrapperMock); + + // let all session 1 and 2 non isolated threads to exit + stopLatch1.countDown(); + stopLatch2.countDown(); + expectedTids2 = new int[]{isoProc1}; + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); + service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500) + TimeUnit.MILLISECONDS.toNanos( + CLEAN_UP_UID_DELAY_MILLIS)); + verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1)); + verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); + verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any()); + // in background, set threads for session 1 then it should not be force paused next time + session1.setThreads(SESSION_TIDS_A); + // the new TIDs pending list should be updated + assertArrayEquals(session1.getTidsInternal(), SESSION_TIDS_A); + assertArrayEquals(session2.getTidsInternal(), expectedTids2); + verifyAllHintsEnabled(session1, false); + verifyAllHintsEnabled(session2, false); + reset(mNativeWrapperMock); + + service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500) + TimeUnit.MILLISECONDS.toNanos( + CLEAN_UP_UID_DELAY_MILLIS)); + verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1), + eq(SESSION_TIDS_A)); + verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr2), + eq(expectedTids2)); + verifyAllHintsEnabled(session1, true); + verifyAllHintsEnabled(session2, true); + } + + private void verifyAllHintsEnabled(AppHintSession session, boolean verifyEnabled) { + session.reportActualWorkDuration2(new WorkDuration[]{makeWorkDuration(1, 3, 2, 1, 1000)}); + session.reportActualWorkDuration(new long[]{1}, new long[]{2}); + session.updateTargetWorkDuration(3); + session.setMode(0, true); + session.sendHint(1); + if (verifyEnabled) { + verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration( + eq(session.mHalSessionPtr), any()); + verify(mNativeWrapperMock, times(1)).halSetMode(eq(session.mHalSessionPtr), anyInt(), + anyBoolean()); + verify(mNativeWrapperMock, times(1)).halUpdateTargetWorkDuration( + eq(session.mHalSessionPtr), anyLong()); + verify(mNativeWrapperMock, times(1)).halSendHint(eq(session.mHalSessionPtr), anyInt()); + } else { + verify(mNativeWrapperMock, never()).halReportActualWorkDuration( + eq(session.mHalSessionPtr), any()); + verify(mNativeWrapperMock, never()).halSetMode(eq(session.mHalSessionPtr), anyInt(), + anyBoolean()); + verify(mNativeWrapperMock, never()).halUpdateTargetWorkDuration( + eq(session.mHalSessionPtr), anyLong()); + verify(mNativeWrapperMock, never()).halSendHint(eq(session.mHalSessionPtr), anyInt()); + } + } + + private int[] createThreads(int threadCount, CountDownLatch stopLatch) + throws InterruptedException { + int[] tids = new int[threadCount]; + AtomicInteger k = new AtomicInteger(0); + CountDownLatch latch = new CountDownLatch(threadCount); + for (int j = 0; j < threadCount; j++) { + Thread thread = new Thread(() -> { + try { + tids[k.getAndIncrement()] = android.os.Process.myTid(); + latch.countDown(); + stopLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + thread.start(); + } + latch.await(); + return tids; + } + + @Test public void testSetMode() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); @@ -457,7 +626,8 @@ public class HintManagerServiceTest { // Set session to background, then the duration would not be updated. service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); - FgThread.getHandler().runWithScissors(() -> { }, 500); + FgThread.getHandler().runWithScissors(() -> { + }, 500); assertFalse(service.mUidObserver.isUidForeground(a.mUid)); a.setMode(0, true); verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean()); @@ -519,7 +689,10 @@ public class HintManagerServiceTest { LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500)); service.mUidObserver.onUidStateChanged(UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500)); + // let the cleanup work proceed + LockSupport.parkNanos( + TimeUnit.MILLISECONDS.toNanos(500) + TimeUnit.MILLISECONDS.toNanos( + CLEAN_UP_UID_DELAY_MILLIS)); } Log.d(TAG, "notifier thread min " + min + " max " + max + " avg " + sum / count); service.mUidObserver.onUidGone(UID, true); diff --git a/services/tests/servicestests/src/com/android/server/power/hint/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/power/hint/TEST_MAPPING new file mode 100644 index 000000000000..2d5df077b128 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/hint/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "postsubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.power.hint" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index 29467f259ac3..a80e2f8ae28c 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -16,10 +16,14 @@ package com.android.server.policy; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManagerGlobal.ADD_OKAY; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -33,18 +37,27 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.content.Context; +import android.os.PowerManager; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; +import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.DisplayPolicy; +import com.android.server.wm.DisplayRotation; +import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; @@ -64,16 +77,27 @@ public class PhoneWindowManagerTests { public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); PhoneWindowManager mPhoneWindowManager; + private ActivityTaskManagerInternal mAtmInternal; + private Context mContext; @Before public void setUp() { mPhoneWindowManager = spy(new PhoneWindowManager()); spyOn(ActivityManager.getService()); + mContext = getInstrumentation().getTargetContext(); + spyOn(mContext); + mAtmInternal = mock(ActivityTaskManagerInternal.class); + LocalServices.addService(ActivityTaskManagerInternal.class, mAtmInternal); + mPhoneWindowManager.mActivityTaskManagerInternal = mAtmInternal; + LocalServices.addService(WindowManagerInternal.class, mock(WindowManagerInternal.class)); } @After public void tearDown() { reset(ActivityManager.getService()); + reset(mContext); + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.removeServiceForTest(WindowManagerInternal.class); } @Test @@ -99,6 +123,60 @@ public class PhoneWindowManagerTests { } @Test + public void testScreenTurnedOff() { + mSetFlagsRule.enableFlags(com.android.window.flags.Flags + .FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY); + doNothing().when(mPhoneWindowManager).updateSettings(any()); + doNothing().when(mPhoneWindowManager).initializeHdmiState(); + final boolean[] isScreenTurnedOff = { false }; + final DisplayPolicy displayPolicy = mock(DisplayPolicy.class); + doAnswer(invocation -> isScreenTurnedOff[0] = true).when(displayPolicy).screenTurnedOff(); + doAnswer(invocation -> !isScreenTurnedOff[0]).when(displayPolicy).isScreenOnEarly(); + doAnswer(invocation -> !isScreenTurnedOff[0]).when(displayPolicy).isScreenOnFully(); + + mPhoneWindowManager.mDefaultDisplayPolicy = displayPolicy; + mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class); + final ActivityTaskManagerInternal.SleepTokenAcquirer tokenAcquirer = + mock(ActivityTaskManagerInternal.SleepTokenAcquirer.class); + doReturn(tokenAcquirer).when(mAtmInternal).createSleepTokenAcquirer(anyString()); + final PowerManager pm = mock(PowerManager.class); + doReturn(true).when(pm).isInteractive(); + doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE)); + + mContext.getMainThreadHandler().runWithScissors(() -> mPhoneWindowManager.init( + new PhoneWindowManager.Injector(mContext, + mock(WindowManagerPolicy.WindowManagerFuncs.class))), 0); + assertThat(isScreenTurnedOff[0]).isFalse(); + assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isFalse(); + + // Skip sleep-token for non-sleep-screen-off. + clearInvocations(tokenAcquirer); + mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */); + verify(tokenAcquirer, never()).acquire(anyInt(), anyBoolean()); + assertThat(isScreenTurnedOff[0]).isTrue(); + + // Apply sleep-token for sleep-screen-off. + mPhoneWindowManager.startedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */); + assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isTrue(); + mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */); + verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY), eq(true)); + + mPhoneWindowManager.finishedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */); + assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isFalse(); + + // Simulate unexpected reversed order: screenTurnedOff -> startedGoingToSleep. The sleep + // token can still be acquired. + isScreenTurnedOff[0] = false; + clearInvocations(tokenAcquirer); + mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */); + verify(tokenAcquirer, never()).acquire(anyInt(), anyBoolean()); + assertThat(displayPolicy.isScreenOnEarly()).isFalse(); + assertThat(displayPolicy.isScreenOnFully()).isFalse(); + mPhoneWindowManager.startedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */); + verify(tokenAcquirer).acquire(eq(DEFAULT_DISPLAY), eq(false)); + } + + @Test public void testCheckAddPermission_withoutAccessibilityOverlay_noAccessibilityAppOpLogged() { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED); @@ -130,11 +208,8 @@ public class PhoneWindowManagerTests { private void mockStartDockOrHome() throws Exception { doNothing().when(ActivityManager.getService()).stopAppSwitches(); - ActivityTaskManagerInternal mMockActivityTaskManagerInternal = - mock(ActivityTaskManagerInternal.class); - when(mMockActivityTaskManagerInternal.startHomeOnDisplay( + when(mAtmInternal.startHomeOnDisplay( anyInt(), anyString(), anyInt(), anyBoolean(), anyBoolean())).thenReturn(false); - mPhoneWindowManager.mActivityTaskManagerInternal = mMockActivityTaskManagerInternal; mPhoneWindowManager.mUserManagerInternal = mock(UserManagerInternal.class); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 897a3da07473..52485eec8505 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -25,7 +25,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; -import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; @@ -1835,7 +1835,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragment tf = createTaskFragment(task); final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( - OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE).build(); + OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE).build(); mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation); assertApplyTransactionAllowed(mTransaction); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 12f46df451fe..48b12f729e08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -90,6 +90,7 @@ import android.util.ArraySet; import android.util.MergedConfiguration; import android.view.ContentRecordingSession; import android.view.IWindow; +import android.view.IWindowSession; import android.view.InputChannel; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -99,6 +100,7 @@ import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.window.ActivityWindowInfo; import android.window.ClientWindowFrames; import android.window.InputTransferToken; import android.window.ScreenCapture; @@ -1216,6 +1218,35 @@ public class WindowManagerServiceTests extends WindowTestsBase { mWm.reportKeepClearAreasChanged(session, window, new ArrayList<>(), new ArrayList<>()); } + @Test + public void testRelayout_appWindowSendActivityWindowInfo() { + mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + // Skip unnecessary operations of relayout. + spyOn(mWm.mWindowPlacerLocked); + doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean()); + + final Task task = createTask(mDisplayContent); + final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "appWindow"); + mWm.mWindowMap.put(win.mClient.asBinder(), win); + + final int w = 100; + final int h = 200; + final ClientWindowFrames outFrames = new ClientWindowFrames(); + final MergedConfiguration outConfig = new MergedConfiguration(); + final SurfaceControl outSurfaceControl = new SurfaceControl(); + final InsetsState outInsetsState = new InsetsState(); + final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array(); + final Bundle outBundle = new Bundle(); + + mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0, + outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); + + final ActivityWindowInfo activityWindowInfo = outBundle.getParcelable( + IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, ActivityWindowInfo.class); + assertEquals(win.mActivityRecord.getActivityWindowInfo(), activityWindowInfo); + } + class TestResultReceiver implements IResultReceiver { public android.os.Bundle resultData; private final IBinder mBinder = mock(IBinder.class); |