diff options
35 files changed, 489 insertions, 688 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0d5ec199b953..7d408888ef21 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4199,7 +4199,7 @@ package android.content.pm { method public void setInstallAsInstantApp(boolean); method public void setInstallAsVirtualPreload(); method public void setRequestDowngrade(boolean); - method @FlaggedApi("android.content.pm.recoverability_detection") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int); method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged(); } @@ -4374,9 +4374,9 @@ package android.content.pm { field public static final int ROLLBACK_DATA_POLICY_RESTORE = 0; // 0x0 field public static final int ROLLBACK_DATA_POLICY_RETAIN = 2; // 0x2 field public static final int ROLLBACK_DATA_POLICY_WIPE = 1; // 0x1 - field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1 - field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0 - field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2 + field public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1 + field public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0 + field public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2 field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0; // 0x0 field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1; // 0x1 field public static final int SYSTEM_APP_STATE_INSTALLED = 2; // 0x2 diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d64ef75a8f7c..4bbbad4d1667 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -3250,7 +3250,6 @@ public class PackageInstaller { */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION) public void setRollbackImpactLevel(@PackageManager.RollbackImpactLevel int impactLevel) { if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { throw new IllegalArgumentException( diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6ae2df2cd7a2..f91b2474fdac 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1619,7 +1619,6 @@ public abstract class PackageManager { * @hide */ @SystemApi - @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION) public static final int ROLLBACK_USER_IMPACT_LOW = 0; /** @@ -1629,7 +1628,6 @@ public abstract class PackageManager { * @hide */ @SystemApi - @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION) public static final int ROLLBACK_USER_IMPACT_HIGH = 1; /** @@ -1638,7 +1636,6 @@ public abstract class PackageManager { * @hide */ @SystemApi - @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION) public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; /** @hide */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 56f0415b40cc..421001f385d4 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -466,6 +466,26 @@ public final class InputMethodManager { private static final long USE_ASYNC_SHOW_HIDE_METHOD = 352594277L; // This is a bug id. /** + * Always return {@code true} when {@link #hideSoftInputFromWindow(IBinder, int)} and + * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver, int, ImeTracker.Token)} is + * called. + * <p> + * Apps can incorrectly rely on the return value of + * {@link #hideSoftInputFromWindow(IBinder, int)} to guess whether the IME was hidden. + * However, it was never a guarantee that the IME will actually hide. + * Therefore, it was recommended using the {@link View.OnApplyWindowInsetsListener}. + * <p> + * Starting Android {@link Build.VERSION_CODES.BAKLAVA}, the return value will always be + * true, independent from whether the request was eventually sent to system server or not. + * Apps that need to know when the IME visibility changes should use + * {@link View.OnApplyWindowInsetsListener}. For apps that target earlier releases, it + * returns an estimate ({@code true} if the IME was showing before). + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA) + private static final long ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW = 395521150L; // bug id + + /** * If {@code true}, avoid calling the * {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService} * by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus} @@ -2531,9 +2551,14 @@ public final class InputMethodManager { * * @param windowToken The token of the window that is making the request, * as returned by {@link View#getWindowToken() View.getWindowToken()}. - * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note: - * this does not return result of the request. For result use {@link ResultReceiver} in - * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)} instead. + * @return <p>For apps targeting Android {@link Build.VERSION_CODES.BAKLAVA}, onwards, it + * will always return {@code true}. To see when the IME is hidden, use + * {@link View.OnApplyWindowInsetsListener} and verify the provided {@link WindowInsets} for + * the visibility of IME. + * + * <p>For apps targeting releases before Android Baklava: returns {@code true} if a request + * was sent to system_server, {@code false} otherwise. Note: This does not return the result + * of that request (i.e. whether the IME was actually hidden). */ public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) { return hideSoftInputFromWindow(windowToken, flags, null); @@ -2563,7 +2588,8 @@ public final class InputMethodManager { * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or * {@link #RESULT_HIDDEN}. * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note: - * this does not return result of the request. For result use {@param resultReceiver} instead. + * This does not return the result of that request (i.e. whether the IME was actually hidden). + * For result use {@param resultReceiver} instead. * * @deprecated The {@link ResultReceiver} is not a reliable way of determining whether the * Input Method is actually shown or hidden. If result is needed, use @@ -2603,7 +2629,10 @@ public final class InputMethodManager { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); ImeTracker.forLatency().onHideFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication); - return false; + // with the flag enabled and targeting Android Baklava onwards, the return value + // should be always true (was false before). + return Flags.refactorInsetsController() && CompatChanges.isChangeEnabled( + ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW); } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); @@ -2618,15 +2647,18 @@ public final class InputMethodManager { // under us. The current input has been closed before (from checkFocus). ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE); - return false; + // with the flag enabled and targeting Android Baklava onwards, the + // return value should be always true (was false before). + return Flags.refactorInsetsController() && CompatChanges.isChangeEnabled( + ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW); } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE); + final boolean imeReqVisible = + (viewRootImpl.getInsetsController().getRequestedVisibleTypes() + & WindowInsets.Type.ime()) != 0; if (resultReceiver != null) { - final boolean imeReqVisible = - (viewRootImpl.getInsetsController().getRequestedVisibleTypes() - & WindowInsets.Type.ime()) != 0; resultReceiver.send( !imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_HIDDEN : InputMethodManager.RESULT_HIDDEN, null); @@ -2642,8 +2674,16 @@ public final class InputMethodManager { viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime(), false /* fromIme */, statsToken); } + if (!CompatChanges.isChangeEnabled( + ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW)) { + // if the IME was not visible before, the additional hide won't change + // anything. + return imeReqVisible; + } } - return true; + // Targeting Android Baklava onwards, this method will always return true. + return CompatChanges.isChangeEnabled( + ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW); } else { return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken, flags, resultReceiver, reason, mAsyncShowHideMethodEnabled); diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 23da7475f7f7..b44620f12bbb 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -101,6 +101,7 @@ public enum DesktopModeFlags { ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true), ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true), ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true), + ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS(Flags::enableModalsFullscreenWithPermission, false), ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true), ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE( Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true), diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 79120b22c205..891c5e382a58 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -16,6 +16,17 @@ flag { } flag { + name: "enable_modals_fullscreen_with_permission" + namespace: "lse_desktop_experience" + description: "Uses permissions to understand if modal fullscreen is allowed for /n" + "transparent activities." + bug: "394714626" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "include_top_transparent_fullscreen_task_in_desktop_heuristic" namespace: "lse_desktop_experience" description: "Whether to include any top transparent fullscreen task launched in desktop /n" diff --git a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java new file mode 100644 index 000000000000..83ae7edd796b --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2025 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.internal.app; + +import android.content.Context; +import android.media.MediaRouter; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.SeekBar; + +import com.android.internal.R; + +/** + * This class manages the content display within the media route controller UI. + */ +public class MediaRouteControllerContentManager { + // Time to wait before updating the volume when the user lets go of the seek bar + // to allow the route provider time to propagate the change and publish a new + // route descriptor. + private static final int VOLUME_UPDATE_DELAY_MILLIS = 250; + + private final MediaRouter.RouteInfo mRoute; + + private LinearLayout mVolumeLayout; + private SeekBar mVolumeSlider; + private boolean mVolumeSliderTouched; + + public MediaRouteControllerContentManager(Context context) { + MediaRouter mRouter = context.getSystemService(MediaRouter.class); + mRoute = mRouter.getSelectedRoute(); + } + + /** + * Starts binding all the views (volume layout, slider, etc.) using the + * given container view. + */ + public void bindViews(View containerView) { + mVolumeLayout = containerView.findViewById(R.id.media_route_volume_layout); + mVolumeSlider = containerView.findViewById(R.id.media_route_volume_slider); + mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + private final Runnable mStopTrackingTouch = new Runnable() { + @Override + public void run() { + if (mVolumeSliderTouched) { + mVolumeSliderTouched = false; + updateVolume(); + } + } + }; + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (mVolumeSliderTouched) { + mVolumeSlider.removeCallbacks(mStopTrackingTouch); + } else { + mVolumeSliderTouched = true; + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Defer resetting mVolumeSliderTouched to allow the media route provider + // a little time to settle into its new state and publish the final + // volume update. + mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + mRoute.requestSetVolume(progress); + } + } + }); + } + + /** + * Updates the volume layout and slider. + */ + public void updateVolume() { + if (!mVolumeSliderTouched) { + if (isVolumeControlAvailable()) { + mVolumeLayout.setVisibility(View.VISIBLE); + mVolumeSlider.setMax(mRoute.getVolumeMax()); + mVolumeSlider.setProgress(mRoute.getVolume()); + } else { + mVolumeLayout.setVisibility(View.GONE); + } + } + } + + private boolean isVolumeControlAvailable() { + return mRoute.getVolumeHandling() == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; + } +} diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java index 621ec5037cbd..c79f3c7bf76f 100644 --- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java +++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java @@ -32,9 +32,6 @@ import android.os.Bundle; import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.SeekBar; import com.android.internal.R; @@ -50,11 +47,6 @@ import com.android.internal.R; * TODO: Move this back into the API, as in the support library media router. */ public class MediaRouteControllerDialog extends AlertDialog { - // Time to wait before updating the volume when the user lets go of the seek bar - // to allow the route provider time to propagate the change and publish a new - // route descriptor. - private static final int VOLUME_UPDATE_DELAY_MILLIS = 250; - private final MediaRouter mRouter; private final MediaRouterCallback mCallback; private final MediaRouter.RouteInfo mRoute; @@ -64,49 +56,19 @@ public class MediaRouteControllerDialog extends AlertDialog { private int[] mMediaRouteOnState = { R.attr.state_activated, R.attr.state_enabled }; private Drawable mCurrentIconDrawable; - private boolean mVolumeControlEnabled = true; - private LinearLayout mVolumeLayout; - private SeekBar mVolumeSlider; - private boolean mVolumeSliderTouched; - - private View mControlView; private boolean mAttachedToWindow; + private final MediaRouteControllerContentManager mContentManager; + public MediaRouteControllerDialog(Context context, int theme) { super(context, theme); + mContentManager = new MediaRouteControllerContentManager(context); mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new MediaRouterCallback(); mRoute = mRouter.getSelectedRoute(); } - /** - * Gets the route that this dialog is controlling. - */ - public MediaRouter.RouteInfo getRoute() { - return mRoute; - } - - /** - * Provides the subclass an opportunity to create a view that will - * be included within the body of the dialog to offer additional media controls - * for the currently playing content. - * - * @param savedInstanceState The dialog's saved instance state. - * @return The media control view, or null if none. - */ - public View onCreateMediaControlView(Bundle savedInstanceState) { - return null; - } - - /** - * Returns whether to enable the volume slider and volume control using the volume keys - * when the route supports it. - */ - public boolean isVolumeControlEnabled() { - return mVolumeControlEnabled; - } - @Override protected void onCreate(Bundle savedInstanceState) { setTitle(mRoute.getName()); @@ -126,60 +88,15 @@ public class MediaRouteControllerDialog extends AlertDialog { setView(customView, 0, 0, 0, 0); super.onCreate(savedInstanceState); + mContentManager.bindViews(customView); + View customPanelView = getWindow().findViewById(R.id.customPanel); if (customPanelView != null) { customPanelView.setMinimumHeight(0); } - mVolumeLayout = customView.findViewById(R.id.media_route_volume_layout); - mVolumeSlider = customView.findViewById(R.id.media_route_volume_slider); - mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - private final Runnable mStopTrackingTouch = new Runnable() { - @Override - public void run() { - if (mVolumeSliderTouched) { - mVolumeSliderTouched = false; - updateVolume(); - } - } - }; - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - if (mVolumeSliderTouched) { - mVolumeSlider.removeCallbacks(mStopTrackingTouch); - } else { - mVolumeSliderTouched = true; - } - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - // Defer resetting mVolumeSliderTouched to allow the media route provider - // a little time to settle into its new state and publish the final - // volume update. - mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser) { - mRoute.requestSetVolume(progress); - } - } - }); mMediaRouteButtonDrawable = obtainMediaRouteButtonDrawable(); - if (update()) { - mControlView = onCreateMediaControlView(savedInstanceState); - FrameLayout controlFrame = - customView.findViewById(R.id.media_route_control_frame); - if (mControlView != null) { - controlFrame.addView(mControlView); - controlFrame.setVisibility(View.VISIBLE); - } else { - controlFrame.setVisibility(View.GONE); - } - } + update(); } @Override @@ -218,14 +135,13 @@ public class MediaRouteControllerDialog extends AlertDialog { return super.onKeyUp(keyCode, event); } - private boolean update() { + private void update() { if (!mRoute.isSelected() || mRoute.isDefault()) { dismiss(); - return false; } setTitle(mRoute.getName()); - updateVolume(); + mContentManager.updateVolume(); Drawable icon = getIconDrawable(); if (icon != mCurrentIconDrawable) { @@ -244,7 +160,6 @@ public class MediaRouteControllerDialog extends AlertDialog { } setIcon(icon); } - return true; } private Drawable obtainMediaRouteButtonDrawable() { @@ -274,23 +189,6 @@ public class MediaRouteControllerDialog extends AlertDialog { } } - private void updateVolume() { - if (!mVolumeSliderTouched) { - if (isVolumeControlAvailable()) { - mVolumeLayout.setVisibility(View.VISIBLE); - mVolumeSlider.setMax(mRoute.getVolumeMax()); - mVolumeSlider.setProgress(mRoute.getVolume()); - } else { - mVolumeLayout.setVisibility(View.GONE); - } - } - } - - private boolean isVolumeControlAvailable() { - return mVolumeControlEnabled && mRoute.getVolumeHandling() == - MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; - } - private final class MediaRouterCallback extends MediaRouter.SimpleCallback { @Override public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { @@ -305,7 +203,7 @@ public class MediaRouteControllerDialog extends AlertDialog { @Override public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { if (route == mRoute) { - updateVolume(); + mContentManager.updateVolume(); } } diff --git a/core/res/res/layout/media_route_controller_dialog.xml b/core/res/res/layout/media_route_controller_dialog.xml index 24a25353f40d..a5cd83be9f6c 100644 --- a/core/res/res/layout/media_route_controller_dialog.xml +++ b/core/res/res/layout/media_route_controller_dialog.xml @@ -41,11 +41,5 @@ android:layout_marginLeft="8dp" android:layout_marginRight="8dp" /> </LinearLayout> - - <!-- Optional content view section. --> - <FrameLayout android:id="@+id/media_route_control_frame" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" /> </LinearLayout> </ScrollView> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 06861b113e64..2c68bd294397 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1743,7 +1743,6 @@ <java-symbol type="id" name="media_route_list" /> <java-symbol type="id" name="media_route_volume_layout" /> <java-symbol type="id" name="media_route_volume_slider" /> - <java-symbol type="id" name="media_route_control_frame" /> <java-symbol type="id" name="media_route_extended_settings_button" /> <java-symbol type="id" name="media_route_progress_bar" /> <java-symbol type="string" name="media_route_chooser_title" /> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 3ada988ba2a3..9a97ae8d61a0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -186,14 +186,16 @@ class DesktopTasksTransitionObserver( for (change in info.changes) { val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) continue - if (change.mode != TRANSIT_CLOSE) continue - if (minimizingTask == null) { - minimizingTask = getMinimizingTaskForClosingTransition(taskInfo) + if ( + TransitionUtil.isClosingMode(change.mode) && + DesktopWallpaperActivity.isWallpaperTask(taskInfo) + ) { + hasWallpaperClosing = true } - if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) { - hasWallpaperClosing = true + if (change.mode == TRANSIT_CLOSE && minimizingTask == null) { + minimizingTask = getMinimizingTaskForClosingTransition(taskInfo) } } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt index 2115f70faad0..af2840e9c34a 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt @@ -45,6 +45,7 @@ constructor( tapl.setExpectedRotation(rotation.value) ChangeDisplayOrientationRule.setRotation(rotation) tapl.enableTransientTaskbar(false) + testApp.exit(wmHelper) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index c29edece5537..dd577f402952 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -24,6 +24,7 @@ import android.content.Context import android.content.Intent import android.os.Binder import android.os.IBinder +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager @@ -168,7 +169,8 @@ class DesktopTasksTransitionObserverTest { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun backNavigation_withCloseTransitionLastTask_taskMinimized() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER) + fun backNavigation_withCloseTransitionLastTask_wallpaperActivityClosed_taskMinimized() { val task = createTaskInfo(1) val transition = mock<IBinder>() whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1) @@ -193,6 +195,35 @@ class DesktopTasksTransitionObserverTest { } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + ) + fun backNavigation_withCloseTransitionLastTask_wallpaperActivityReordered_taskMinimized() { + val task = createTaskInfo(1) + val transition = mock<IBinder>() + whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1) + whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false) + whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId) + + transitionObserver.onTransitionReady( + transition = transition, + info = createBackNavigationTransition(task, TRANSIT_CLOSE, true, TRANSIT_TO_BACK), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).minimizeTask(task.displayId, task.taskId) + val pendingTransition = + DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( + transition, + task.taskId, + isLastTask = true, + ) + verify(mixedHandler).addPendingMixedTransition(pendingTransition) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun backNavigation_nullTaskInfo_taskNotMinimized() { val task = createTaskInfo(1) @@ -434,6 +465,7 @@ class DesktopTasksTransitionObserverTest { task: RunningTaskInfo?, type: Int = TRANSIT_TO_BACK, withWallpaper: Boolean = false, + wallpaperChangeMode: Int = TRANSIT_CLOSE, ): TransitionInfo { return TransitionInfo(type, /* flags= */ 0).apply { addChange( diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt index ae75e6c089ca..a1d362a4a11d 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt @@ -39,7 +39,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf @@ -175,21 +174,7 @@ fun Expandable( val wrappedContent = remember(content) { movableContentOf { expandable: Expandable -> - CompositionLocalProvider(LocalContentColor provides contentColor) { - // We make sure that the content itself (wrapped by the background) is at least - // 40.dp, which is the same as the M3 buttons. This applies even if onClick is - // null, to make it easier to write expandables that are sometimes clickable and - // sometimes not. There shouldn't be any Expandable smaller than 40dp because if - // the expandable is not clickable directly, then something in its content - // should be (and with a size >= 40dp). - val minSize = 40.dp - Box( - Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), - contentAlignment = Alignment.Center, - ) { - content(expandable) - } - } + WrappedContent(expandable, contentColor, content) } } @@ -209,11 +194,7 @@ fun Expandable( // Make sure we don't read animatorState directly here to avoid recomposition every time the // state changes (i.e. every frame of the animation). - val isAnimating by remember { - derivedStateOf { - controller.animatorState.value != null && controller.overlay.value != null - } - } + val isAnimating = controller.isAnimating // If this expandable is expanded when it's being directly clicked on, let's ensure that it has // the minimum interactive size followed by all M3 components (48.dp). @@ -237,58 +218,36 @@ fun Expandable( // animating. AnimatedContentInOverlay( color, - controller.boundsInComposeViewRoot.value.size, - controller.animatorState, - controller.overlay.value + controller.boundsInComposeViewRoot.size, + controller.overlay ?: error("AnimatedContentInOverlay shouldn't be composed with null overlay."), controller, wrappedContent, controller.composeViewRoot, - { controller.currentComposeViewInOverlay.value = it }, + { controller.currentComposeViewInOverlay = it }, controller.density, ) } - controller.isDialogShowing.value -> { + controller.isDialogShowing -> { Box( modifier .updateExpandableSize() .then(minInteractiveSizeModifier) .drawWithContent { /* Don't draw anything when the dialog is shown. */ } - .onGloballyPositioned { - controller.boundsInComposeViewRoot.value = it.boundsInRoot() - } + .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() } ) { wrappedContent(controller.expandable) } } else -> { - val clickModifier = - if (onClick != null) { - if (interactionSource != null) { - // If the caller provided an interaction source, then that means that they - // will draw the click indication themselves. - Modifier.clickable(interactionSource, indication = null) { - onClick(controller.expandable) - } - } else { - // If no interaction source is provided, we draw the default indication (a - // ripple) and make sure it's clipped by the expandable shape. - Modifier.clip(shape).clickable { onClick(controller.expandable) } - } - } else { - Modifier - } - Box( modifier .updateExpandableSize() .then(minInteractiveSizeModifier) - .then(clickModifier) + .then(clickModifier(controller, onClick, interactionSource)) .background(color, shape) .border(controller) - .onGloballyPositioned { - controller.boundsInComposeViewRoot.value = it.boundsInRoot() - } + .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() } ) { wrappedContent(controller.expandable) } @@ -296,12 +255,55 @@ fun Expandable( } } +@Composable +private fun WrappedContent( + expandable: Expandable, + contentColor: Color, + content: @Composable (Expandable) -> Unit, +) { + CompositionLocalProvider(LocalContentColor provides contentColor) { + // We make sure that the content itself (wrapped by the background) is at least 40.dp, which + // is the same as the M3 buttons. This applies even if onClick is null, to make it easier to + // write expandables that are sometimes clickable and sometimes not. There shouldn't be any + // Expandable smaller than 40dp because if the expandable is not clickable directly, then + // something in its content should be (and with a size >= 40dp). + val minSize = 40.dp + Box( + Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), + contentAlignment = Alignment.Center, + ) { + content(expandable) + } + } +} + +private fun clickModifier( + controller: ExpandableControllerImpl, + onClick: ((Expandable) -> Unit)?, + interactionSource: MutableInteractionSource?, +): Modifier { + if (onClick == null) { + return Modifier + } + + if (interactionSource != null) { + // If the caller provided an interaction source, then that means that they will draw the + // click indication themselves. + return Modifier.clickable(interactionSource, indication = null) { + onClick(controller.expandable) + } + } + + // If no interaction source is provided, we draw the default indication (a ripple) and make sure + // it's clipped by the expandable shape. + return Modifier.clip(controller.shape).clickable { onClick(controller.expandable) } +} + /** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */ @Composable private fun AnimatedContentInOverlay( color: Color, sizeInOriginalLayout: Size, - animatorState: State<TransitionAnimator.State?>, overlay: ViewGroupOverlay, controller: ExpandableControllerImpl, content: @Composable (Expandable) -> Unit, @@ -324,7 +326,7 @@ private fun AnimatedContentInOverlay( // so that its content is laid out exactly the same way. .requiredSize(with(density) { sizeInOriginalLayout.toDpSize() }) .drawWithContent { - val animatorState = animatorState.value ?: return@drawWithContent + val animatorState = controller.animatorState ?: return@drawWithContent // Scale the content with the background while keeping its aspect ratio. val widthRatio = @@ -348,7 +350,8 @@ private fun AnimatedContentInOverlay( setContent { Box( Modifier.fillMaxSize().drawWithContent { - val animatorState = animatorState.value ?: return@drawWithContent + val animatorState = + controller.animatorState ?: return@drawWithContent if (!animatorState.visible) { return@drawWithContent } @@ -385,7 +388,7 @@ private fun AnimatedContentInOverlay( overlay.add(composeViewInOverlay) val startState = - animatorState.value + controller.animatorState ?: throw IllegalStateException( "AnimatedContentInOverlay shouldn't be composed with null animatorState." ) diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index c5d2802c8941..377ea96c5723 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -25,10 +25,11 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size @@ -53,6 +54,9 @@ interface ExpandableController { /** The [Expandable] controlled by this controller. */ val expandable: Expandable + /** Whether this controller is currently animating a launch. */ + val isAnimating: Boolean + /** Called when the [Expandable] stop being included in the composition. */ fun onDispose() } @@ -73,24 +77,9 @@ fun rememberExpandableController( val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current - // The current animation state, if we are currently animating a dialog or activity. - val animatorState = remember { mutableStateOf<TransitionAnimator.State?>(null) } - - // Whether a dialog controlled by this ExpandableController is currently showing. - val isDialogShowing = remember { mutableStateOf(false) } - - // The overlay in which we should animate the launch. - val overlay = remember { mutableStateOf<ViewGroupOverlay?>(null) } - - // The current [ComposeView] being animated in the [overlay], if any. - val currentComposeViewInOverlay = remember { mutableStateOf<View?>(null) } - - // The bounds in [composeViewRoot] of the expandable controlled by this controller. - val boundsInComposeViewRoot = remember { mutableStateOf(Rect.Zero) } - // Whether this composable is still composed. We only do the dialog exit animation if this is // true. - val isComposed = remember { mutableStateOf(true) } + var isComposed by remember { mutableStateOf(true) } val controller = remember( @@ -109,19 +98,14 @@ fun rememberExpandableController( borderStroke, composeViewRoot, density, - animatorState, - isDialogShowing, - overlay, - currentComposeViewInOverlay, - boundsInComposeViewRoot, layoutDirection, - isComposed, + { isComposed }, ) } DisposableEffect(Unit) { onDispose { - isComposed.value = false + isComposed = false if (TransitionAnimator.returnAnimationsEnabled()) { controller.onDispose() } @@ -138,14 +122,27 @@ internal class ExpandableControllerImpl( internal val borderStroke: BorderStroke?, internal val composeViewRoot: View, internal val density: Density, - internal val animatorState: MutableState<TransitionAnimator.State?>, - internal val isDialogShowing: MutableState<Boolean>, - internal val overlay: MutableState<ViewGroupOverlay?>, - internal val currentComposeViewInOverlay: MutableState<View?>, - internal val boundsInComposeViewRoot: MutableState<Rect>, private val layoutDirection: LayoutDirection, - private val isComposed: State<Boolean>, + private val isComposed: () -> Boolean, ) : ExpandableController { + /** The current animation state, if we are currently animating a dialog or activity. */ + var animatorState by mutableStateOf<TransitionAnimator.State?>(null) + private set + + /** Whether a dialog controlled by this ExpandableController is currently showing. */ + var isDialogShowing by mutableStateOf(false) + private set + + /** The overlay in which we should animate the launch. */ + var overlay by mutableStateOf<ViewGroupOverlay?>(null) + private set + + /** The current [ComposeView] being animated in the [overlay], if any. */ + var currentComposeViewInOverlay by mutableStateOf<View?>(null) + + /** The bounds in [composeViewRoot] of the expandable controlled by this controller. */ + var boundsInComposeViewRoot by mutableStateOf(Rect.Zero) + /** The [ActivityTransitionAnimator.Controller] to be cleaned up [onDispose]. */ private var activityControllerForDisposal: ActivityTransitionAnimator.Controller? = null @@ -158,7 +155,7 @@ internal class ExpandableControllerImpl( returnCujType: Int?, isEphemeral: Boolean, ): ActivityTransitionAnimator.Controller? { - if (!isComposed.value) { + if (!isComposed()) { return null } @@ -174,7 +171,7 @@ internal class ExpandableControllerImpl( override fun dialogTransitionController( cuj: DialogCuj? ): DialogTransitionAnimator.Controller? { - if (!isComposed.value) { + if (!isComposed()) { return null } @@ -182,6 +179,8 @@ internal class ExpandableControllerImpl( } } + override val isAnimating: Boolean by derivedStateOf { animatorState != null && overlay != null } + override fun onDispose() { activityControllerForDisposal?.onDispose() activityControllerForDisposal = null @@ -204,7 +203,7 @@ internal class ExpandableControllerImpl( override val isLaunching: Boolean = true override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - animatorState.value = null + animatorState = null } override fun onTransitionAnimationProgress( @@ -214,7 +213,7 @@ internal class ExpandableControllerImpl( ) { // We copy state given that it's always the same object that is mutated by // ActivityTransitionAnimator. - animatorState.value = + animatorState = TransitionAnimator.State( state.top, state.bottom, @@ -227,13 +226,11 @@ internal class ExpandableControllerImpl( // Force measure and layout the ComposeView in the overlay whenever the animation // state changes. - currentComposeViewInOverlay.value?.let { - measureAndLayoutComposeViewInOverlay(it, state) - } + currentComposeViewInOverlay?.let { measureAndLayoutComposeViewInOverlay(it, state) } } override fun createAnimatorState(): TransitionAnimator.State { - val boundsInRoot = boundsInComposeViewRoot.value + val boundsInRoot = boundsInComposeViewRoot val outline = shape.createOutline( Size(boundsInRoot.width, boundsInRoot.height), @@ -285,7 +282,7 @@ internal class ExpandableControllerImpl( private fun rootLocationOnScreen(): Offset { composeViewRoot.getLocationOnScreen(rootLocationOnScreen) - val boundsInRoot = boundsInComposeViewRoot.value + val boundsInRoot = boundsInComposeViewRoot val x = rootLocationOnScreen[0] + boundsInRoot.left val y = rootLocationOnScreen[1] + boundsInRoot.top return Offset(x, y) @@ -319,14 +316,14 @@ internal class ExpandableControllerImpl( override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { delegate.onTransitionAnimationStart(isExpandingFullyAbove) - overlay.value = transitionContainer.overlay as ViewGroupOverlay + overlay = transitionContainer.overlay as ViewGroupOverlay cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) } } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { cujType?.let { InteractionJankMonitor.getInstance().end(it) } delegate.onTransitionAnimationEnd(isExpandingFullyAbove) - overlay.value = null + overlay = null } } } @@ -339,14 +336,14 @@ internal class ExpandableControllerImpl( override fun startDrawingInOverlayOf(viewGroup: ViewGroup) { val newOverlay = viewGroup.overlay as ViewGroupOverlay - if (newOverlay != overlay.value) { - overlay.value = newOverlay + if (newOverlay != overlay) { + overlay = newOverlay } } override fun stopDrawingInOverlay() { - if (overlay.value != null) { - overlay.value = null + if (overlay != null) { + overlay = null } } @@ -357,7 +354,7 @@ internal class ExpandableControllerImpl( delegate.onTransitionAnimationEnd(isExpandingFullyAbove) // Make sure we don't draw this expandable when the dialog is showing. - isDialogShowing.value = true + isDialogShowing = true } } } @@ -367,16 +364,17 @@ internal class ExpandableControllerImpl( return object : TransitionAnimator.Controller by delegate { override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { delegate.onTransitionAnimationEnd(isExpandingFullyAbove) - isDialogShowing.value = false + isDialogShowing = false } } } - override fun shouldAnimateExit(): Boolean = - isComposed.value && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown + override fun shouldAnimateExit(): Boolean { + return isComposed() && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown + } override fun onExitAnimationCancelled() { - isDialogShowing.value = false + isDialogShowing = false } override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? { diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt index f5c3a834a8d7..089da4b932b2 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt @@ -42,13 +42,19 @@ import androidx.savedstate.setViewTreeSavedStateRegistryOwner @Composable fun Modifier.drawInOverlay(): Modifier { val containerState = remember { ContainerState() } + FullScreenComposeViewInOverlay { Modifier.container(containerState) } + return this.drawInContainer(containerState, enabled = { true }) +} + +@Composable +internal fun FullScreenComposeViewInOverlay(modifier: (ComposeView) -> Modifier = { Modifier }) { val context = LocalContext.current val localView = LocalView.current val compositionContext = rememberCompositionContext() val displayMetrics = context.resources.displayMetrics val displaySize = IntSize(displayMetrics.widthPixels, displayMetrics.heightPixels) - DisposableEffect(containerState, context, localView, compositionContext, displaySize) { + DisposableEffect(context, localView, compositionContext, displaySize) { val overlay = localView.rootView.overlay as ViewGroupOverlay val view = ComposeView(context).apply { @@ -59,7 +65,8 @@ fun Modifier.drawInOverlay(): Modifier { setViewTreeViewModelStoreOwner(localView.findViewTreeViewModelStoreOwner()) setViewTreeSavedStateRegistryOwner(localView.findViewTreeSavedStateRegistryOwner()) - setContent { Box(Modifier.fillMaxSize().container(containerState)) } + val view = this + setContent { Box(modifier(view).fillMaxSize()) } } overlay.add(view) @@ -74,6 +81,4 @@ fun Modifier.drawInOverlay(): Modifier { onDispose { overlay.remove(view) } } - - return this.drawInContainer(containerState, enabled = { true }) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt index 35368ca8734d..9498daaf0b07 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt @@ -60,6 +60,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() { policies, shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked, keyguardRepository, + displayRepository, ) @Test @@ -90,6 +91,30 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() { } @Test + fun displayId_afterDisplayDisconnected_fallsBackToDefaultDisplay() = + testScope.runTest { + val underTest = createUnderTest() + globalSettings.putString( + DEVELOPMENT_SHADE_DISPLAY_AWARENESS, + FakeShadeDisplayPolicy.name, + ) + val displayId by collectLastValue(underTest.displayId) + + displayRepository.addDisplay(displayId = 1) + + FakeShadeDisplayPolicy.setDisplayId(1) + assertThat(displayId).isEqualTo(1) + + // Let's disconnect and make sure it goes back to the default one + displayRepository.removeDisplay(displayId = 1) + assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) + + // Let's re-connect it and make sure it goes back to the non-default one + displayRepository.addDisplay(displayId = 1) + assertThat(displayId).isEqualTo(1) + } + + @Test fun policy_updatesBasedOnSettingValue_defaultDisplay() = testScope.runTest { val underTest = createUnderTest() diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt index 4eb707297073..2a14ca44386d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt @@ -20,6 +20,7 @@ import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS import android.view.Display import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked import com.android.systemui.shade.display.ShadeDisplayPolicy @@ -45,7 +46,12 @@ interface ShadeDisplaysRepository { val currentPolicy: ShadeDisplayPolicy } -/** Keeps the policy and propagates the display id for the shade from it. */ +/** + * Keeps the policy and propagates the display id for the shade from it. + * + * If the display set by the policy is not available (e.g. after the cable is disconnected), this + * falls back to the [Display.DEFAULT_DISPLAY]. + */ @SysUISingleton class ShadeDisplaysRepositoryImpl @Inject @@ -56,6 +62,7 @@ constructor( policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>, @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean, keyguardRepository: KeyguardRepository, + displayRepository: DisplayRepository, ) : ShadeDisplaysRepository { private val policy: StateFlow<ShadeDisplayPolicy> = @@ -73,7 +80,12 @@ constructor( .distinctUntilChanged() .stateIn(bgScope, SharingStarted.Eagerly, defaultPolicy) - private val displayIdFromPolicy: Flow<Int> = policy.flatMapLatest { it.displayId } + private val displayIdFromPolicy: Flow<Int> = + policy + .flatMapLatest { it.displayId } + .combine(displayRepository.displayIds) { policyDisplayId, availableIds -> + if (policyDisplayId !in availableIds) Display.DEFAULT_DISPLAY else policyDisplayId + } private val keyguardAwareDisplayPolicy: Flow<Int> = if (!shadeOnDefaultDisplayWhenLocked) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt index 1f534a5c191a..4ebdb6023268 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt @@ -26,7 +26,6 @@ import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.shade.domain.interactor.NotificationShadeElement import com.android.systemui.shade.domain.interactor.QSShadeElement import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement -import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import dagger.Lazy import java.util.concurrent.atomic.AtomicReference @@ -53,7 +52,6 @@ class StatusBarTouchShadeDisplayPolicy constructor( displayRepository: DisplayRepository, @Background private val backgroundScope: CoroutineScope, - private val shadeInteractor: Lazy<ShadeInteractor>, private val qsShadeElement: Lazy<QSShadeElement>, private val notificationElement: Lazy<NotificationShadeElement>, ) : ShadeDisplayPolicy, ShadeExpansionIntent { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt index ba7557ef7f71..26a441bc8ca3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt @@ -30,7 +30,6 @@ import com.android.systemui.shade.display.ShadeExpansionIntent import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy import com.android.systemui.shade.domain.interactor.notificationElement import com.android.systemui.shade.domain.interactor.qsElement -import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.util.settings.fakeGlobalSettings val Kosmos.defaultShadeDisplayPolicy: DefaultDisplayShadePolicy by @@ -49,9 +48,8 @@ val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by StatusBarTouchShadeDisplayPolicy( displayRepository = displayRepository, backgroundScope = testScope.backgroundScope, - shadeInteractor = { shadeInteractor }, - notificationElement = { notificationElement }, qsShadeElement = { qsElement }, + notificationElement = { notificationElement }, ) } val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by @@ -65,6 +63,7 @@ val Kosmos.shadeDisplaysRepository: ShadeDisplaysRepository by defaultPolicy = defaultShadeDisplayPolicy, shadeOnDefaultDisplayWhenLocked = true, keyguardRepository = keyguardRepository, + displayRepository = displayRepository, ) } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 15688c0f7366..28117470e7a3 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -204,6 +204,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -1023,6 +1024,7 @@ final class InstallPackageHelper { */ void installPackagesTraced(List<InstallRequest> requests, MoveInfo moveInfo) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); + boolean pendingForDexopt = false; boolean success = false; final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size()); final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size()); @@ -1036,17 +1038,41 @@ final class InstallPackageHelper { if (reconciledPackages == null) { return; } + if (renameAndUpdatePaths(requests)) { // rename before dexopt because art will encoded the path in the odex/vdex file if (Flags.improveInstallFreeze()) { - prepPerformDexoptIfNeeded(reconciledPackages); - } - if (commitInstallPackages(reconciledPackages)) { - success = true; + pendingForDexopt = true; + final Runnable actionsAfterDexopt = () -> + doPostDexopt(reconciledPackages, requests, + createdAppId, moveInfo, acquireTime); + prepPerformDexoptIfNeeded(reconciledPackages, actionsAfterDexopt); + } else { + if (commitInstallPackages(reconciledPackages)) { + success = true; + } } } } } finally { + if (!pendingForDexopt) { + completeInstallProcess(requests, createdAppId, success); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + doPostInstall(requests, moveInfo); + releaseWakeLock(acquireTime, requests.size()); + } + } + } + + void doPostDexopt(List<ReconciledPackage> reconciledPackages, + List<InstallRequest> requests, Map<String, Boolean> createdAppId, + MoveInfo moveInfo, long acquireTime) { + boolean success = false; + try { + if (commitInstallPackages(reconciledPackages)) { + success = true; + } + } finally { completeInstallProcess(requests, createdAppId, success); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); doPostInstall(requests, moveInfo); @@ -1123,7 +1149,7 @@ final class InstallPackageHelper { throws PackageManagerException { final int userId = installRequest.getUserId(); if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT - && !mPm.mUserManager.exists(userId)) { + && !ArrayUtils.contains(allUsers, userId)) { throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER, "User " + userId + " doesn't exist or has been removed"); } @@ -1155,7 +1181,9 @@ final class InstallPackageHelper { } } - private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages) { + private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages, + Runnable actionsAfterDexopt) { + List<CompletableFuture<Void>> completableFutures = new ArrayList<>(); for (ReconciledPackage reconciledPkg : reconciledPackages) { final InstallRequest request = reconciledPkg.mInstallRequest; // prepare profiles @@ -1171,6 +1199,7 @@ final class InstallPackageHelper { mSharedLibraries.executeSharedLibrariesUpdate(request.getParsedPackage(), ps, null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers); } + try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) { final int[] newUsers = getNewUsers(request, allUsers); // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088) @@ -1182,11 +1211,22 @@ final class InstallPackageHelper { } } catch (PackageManagerException e) { request.setError(e.error, e.getMessage()); - return; + break; } request.setKeepArtProfile(true); - // TODO(b/388159696): Use performDexoptIfNeededAsync. - DexOptHelper.performDexoptIfNeeded(request, mDexManager, null /* installLock */); + + CompletableFuture<Void> future = + DexOptHelper.performDexoptIfNeededAsync(request, mDexManager); + completableFutures.add(future); + } + + if (!completableFutures.isEmpty()) { + CompletableFuture<Void> allFutures = + CompletableFuture.allOf( + completableFutures.toArray(CompletableFuture[]::new)); + var unused = allFutures.thenRun(() -> mPm.mHandler.post(actionsAfterDexopt)); + } else { + actionsAfterDexopt.run(); } } @@ -2759,6 +2799,7 @@ final class InstallPackageHelper { | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); } + // run synchronous dexopt if the freeze improvement is not supported DexOptHelper.performDexoptIfNeeded( installRequest, mDexManager, mPm.mInstallLock.getRawLock()); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index e1fcc6650650..2d0bb258e89f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -805,22 +805,20 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } - if (Flags.recoverabilityDetection()) { - if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH - || params.rollbackImpactLevel - == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) { - if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { - throw new IllegalArgumentException( - "Can't set rollbackImpactLevel when rollback is not enabled"); - } - if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission"); - } - } else if (params.rollbackImpactLevel < 0) { - throw new IllegalArgumentException("rollbackImpactLevel can't be negative."); + if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH + || params.rollbackImpactLevel + == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) { + if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { + throw new IllegalArgumentException( + "Can't set rollbackImpactLevel when rollback is not enabled"); + } + if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission"); } + } else if (params.rollbackImpactLevel < 0) { + throw new IllegalArgumentException("rollbackImpactLevel can't be negative."); } boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index aa235c2258ac..cf598e89c988 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3561,9 +3561,6 @@ class PackageManagerShellCommand extends ShellCommand { sessionParams.setEnableRollback(true, rollbackStrategy); break; case "--rollback-impact-level": - if (!Flags.recoverabilityDetection()) { - throw new IllegalArgumentException("Unknown option " + opt); - } int rollbackImpactLevel = Integer.parseInt(peekNextArg()); if (rollbackImpactLevel < PackageManager.ROLLBACK_USER_IMPACT_LOW || rollbackImpactLevel @@ -4775,11 +4772,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --full: cause the app to be installed as a non-ephemeral full app"); pw.println(" --enable-rollback: enable rollbacks for the upgrade."); pw.println(" 0=restore (default), 1=wipe, 2=retain"); - if (Flags.recoverabilityDetection()) { - pw.println( - " --rollback-impact-level: set device impact required for rollback."); - pw.println(" 0=low (default), 1=high, 2=manual only"); - } + pw.println( + " --rollback-impact-level: set device impact required for rollback."); + pw.println(" 0=low (default), 1=high, 2=manual only"); pw.println(" --install-location: force the install location:"); pw.println(" 0=auto, 1=internal only, 2=prefer external"); pw.println(" --install-reason: indicates why the app is being installed:"); diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index dd60a155f2fb..8510ee70cc56 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -179,8 +179,7 @@ final class VerifyingSession { // Perform package verification and enable rollback (unless we are simply moving the // package). if (!mOriginInfo.mExisting) { - final boolean verifyForRollback = Flags.recoverabilityDetection() - ? !isARollback() : true; + final boolean verifyForRollback = !isARollback(); if (!isApex() && !isArchivedInstallation() && verifyForRollback) { // TODO(b/182426975): treat APEX as APK when APK verification is concerned sendApkVerificationRequest(pkgLite); diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index ab756f2a755b..5347ca46ab31 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -29,7 +29,6 @@ import android.annotation.WorkerThread; import android.content.Context; import android.content.Intent; import android.content.IntentSender; -import android.content.pm.Flags; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -965,9 +964,7 @@ class Rollback { ipw.println("-stateDescription: " + mStateDescription); ipw.println("-timestamp: " + getTimestamp()); ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis()); - if (Flags.recoverabilityDetection()) { - ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel()); - } + ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel()); ipw.println("-isStaged: " + isStaged()); ipw.println("-originalSessionId: " + getOriginalSessionId()); ipw.println("-packages:"); diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 2e6be5bb56a8..9ed52d8b3504 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -1244,17 +1244,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba rollback.makeAvailable(); mPackageHealthObserver.notifyRollbackAvailable(rollback.info); - if (Flags.recoverabilityDetection()) { - if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) { - // TODO(zezeozue): Provide API to explicitly start observing instead - // of doing this for all rollbacks. If we do this for all rollbacks, - // should document in PackageInstaller.SessionParams#setEnableRollback - // After enabling and committing any rollback, observe packages and - // prepare to rollback if packages crashes too frequently. - mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(), - mRollbackLifetimeDurationInMillis, mPackageHealthObserver); - } - } else { + if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) { + // TODO(zezeozue): Provide API to explicitly start observing instead + // of doing this for all rollbacks. If we do this for all rollbacks, + // should document in PackageInstaller.SessionParams#setEnableRollback + // After enabling and committing any rollback, observe packages and + // prepare to rollback if packages crashes too frequently. mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(), mRollbackLifetimeDurationInMillis, mPackageHealthObserver); } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 50db1e4ac30e..6dc40323f2ee 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -197,9 +197,7 @@ class RollbackStore { json.put("isStaged", rollback.isStaged()); json.put("causePackages", versionedPackagesToJson(rollback.getCausePackages())); json.put("committedSessionId", rollback.getCommittedSessionId()); - if (Flags.recoverabilityDetection()) { - json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel()); - } + json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel()); return json; } @@ -211,11 +209,9 @@ class RollbackStore { versionedPackagesFromJson(json.getJSONArray("causePackages")), json.getInt("committedSessionId")); - if (Flags.recoverabilityDetection()) { - // to make it backward compatible. - rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel", - PackageManager.ROLLBACK_USER_IMPACT_LOW)); - } + // to make it backward compatible. + rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel", + PackageManager.ROLLBACK_USER_IMPACT_LOW)); return rollbackInfo; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2bbd69c65eb8..e158310455ac 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1279,12 +1279,6 @@ public final class SystemServer implements Dumpable { if (!Flags.refactorCrashrecovery()) { // Initialize RescueParty. CrashRecoveryAdaptor.rescuePartyRegisterHealthObserver(mSystemContext); - if (!Flags.recoverabilityDetection()) { - // Now that we have the bare essentials of the OS up and running, take - // note that we just booted, which might send out a rescue party if - // we're stuck in a runtime restart loop. - CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext); - } } @@ -1558,14 +1552,6 @@ public final class SystemServer implements Dumpable { boolean enableVrService = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE); - if (!Flags.recoverabilityDetection()) { - // For debugging RescueParty - if (Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.crash_system", false)) { - throw new RuntimeException(); - } - } - try { final String SECONDARY_ZYGOTE_PRELOAD = "SecondaryZygotePreload"; // We start the preload ~1s before the webview factory preparation, to @@ -3091,13 +3077,11 @@ public final class SystemServer implements Dumpable { CrashRecoveryAdaptor.initializeCrashrecoveryModuleService(mSystemServiceManager); t.traceEnd(); } else { - if (Flags.recoverabilityDetection()) { - // Now that we have the essential services needed for mitigations, register the boot - // with package watchdog. - // Note that we just booted, which might send out a rescue party if we're stuck in a - // runtime restart loop. - CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext); - } + // Now that we have the essential services needed for mitigations, register the boot + // with package watchdog. + // Note that we just booted, which might send out a rescue party if we're stuck in a + // runtime restart loop. + CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext); } t.traceBegin("MakeDisplayManagerServiceReady"); @@ -3511,12 +3495,10 @@ public final class SystemServer implements Dumpable { * are updated outside of OTA; and to avoid breaking dependencies from system into apexes. */ private void startApexServices(@NonNull TimingsTraceAndSlog t) { - if (Flags.recoverabilityDetection()) { - // For debugging RescueParty - if (Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.crash_system", false)) { - throw new RuntimeException(); - } + // For debugging RescueParty + if (Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("debug.crash_system", false)) { + throw new RuntimeException(); } t.traceBegin("startApexServices"); diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 6c73f0a33a67..eda5e8613dba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -23,7 +23,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; @@ -34,8 +33,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import android.content.ContentResolver; @@ -43,12 +40,8 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; -import android.crashrecovery.flags.Flags; import android.os.RecoverySystem; import android.os.SystemProperties; -import android.os.UserHandle; -import android.platform.test.flag.junit.FlagsParameterization; -import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.Settings; @@ -59,14 +52,8 @@ import com.android.server.am.SettingsToPropertiesMapper; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -75,34 +62,19 @@ import org.mockito.stubbing.Answer; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.concurrent.TimeUnit; /** * Test RescueParty. */ -@RunWith(Parameterized.class) public class RescuePartyTest { - @Rule - public SetFlagsRule mSetFlagsRule; private static final long CURRENT_NETWORK_TIME_MILLIS = 0L; - private static final String FAKE_NATIVE_NAMESPACE1 = "native1"; - private static final String FAKE_NATIVE_NAMESPACE2 = "native2"; - private static final String[] FAKE_RESET_NATIVE_NAMESPACES = - {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2}; private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1); private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; - private static final String CALLING_PACKAGE1 = "com.package.name1"; - private static final String CALLING_PACKAGE2 = "com.package.name2"; - private static final String CALLING_PACKAGE3 = "com.package.name3"; private static final String PERSISTENT_PACKAGE = "com.persistent.package"; private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package"; - private static final String NAMESPACE1 = "namespace1"; - private static final String NAMESPACE2 = "namespace2"; - private static final String NAMESPACE3 = "namespace3"; - private static final String NAMESPACE4 = "namespace4"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = @@ -126,21 +98,6 @@ public class RescuePartyTest { // Mock only sysprop apis private PackageWatchdog.BootThreshold mSpyBootThreshold; - @Captor - private ArgumentCaptor<DeviceConfig.MonitorCallback> mMonitorCallbackCaptor; - @Captor - private ArgumentCaptor<List<String>> mPackageListCaptor; - - @Parameters(name = "{0}") - public static List<FlagsParameterization> getFlags() { - return FlagsParameterization.allCombinationsOf( - Flags.FLAG_RECOVERABILITY_DETECTION); - } - - public RescuePartyTest(FlagsParameterization flags) { - mSetFlagsRule = new SetFlagsRule(flags); - } - @Before public void setUp() throws Exception { mSession = @@ -419,25 +376,6 @@ public class RescuePartyTest { } - private void verifySettingsResets(int resetMode, String[] resetNamespaces, - HashMap<String, Integer> configResetVerifiedTimesMap) { - verifyOnlySettingsReset(resetMode); - } - - private void verifyOnlySettingsReset(int resetMode) { - verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, - resetMode, UserHandle.USER_SYSTEM)); - verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(), - eq(resetMode), anyInt())); - } - - private void verifyNoSettingsReset(int resetMode) { - verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, - resetMode, UserHandle.USER_SYSTEM), never()); - verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(), - eq(resetMode), anyInt()), never()); - } - private void noteBoot(int mitigationCount) { RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount); } diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp index 8eae9c7d71fa..e030b3f19e4f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp +++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp @@ -32,18 +32,18 @@ android_test { static_libs: [ "androidx.test.core", "androidx.test.runner", + "flag-junit", "mockito-target-extended-minus-junit4", "services.core", "truth", - "flag-junit", ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { "true": ["service-crashrecovery-pre-jarjar"], default: [], }), libs: [ - "android.test.mock.stubs.system", "android.test.base.stubs.system", + "android.test.mock.stubs.system", "android.test.runner.stubs.system", ], @@ -55,7 +55,9 @@ android_test { certificate: "platform", platform_apis: true, test_suites: [ - "device-tests", "automotive-tests", + "device-tests", + "mts-crashrecovery", ], + min_sdk_version: "36", } diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp index 5a802d9de2ff..36b064b9b090 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp @@ -30,18 +30,18 @@ android_test { static_libs: [ "androidx.test.runner", + "flag-junit", "mockito-target-extended-minus-junit4", "services.core", "truth", - "flag-junit", ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { "true": ["service-crashrecovery-pre-jarjar"], default: [], }), libs: [ - "android.test.mock.stubs.system", "android.test.base.stubs.system", + "android.test.mock.stubs.system", "android.test.runner.stubs.system", ], @@ -53,9 +53,11 @@ android_test { certificate: "platform", platform_apis: true, test_suites: [ - "device-tests", "automotive-tests", + "device-tests", + "mts-crashrecovery", ], + min_sdk_version: "36", } test_module_config { diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java index 347dc81c6734..fb4d81ac831c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java @@ -43,7 +43,6 @@ import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; -import android.crashrecovery.flags.Flags; import android.os.Handler; import android.os.MessageQueue; import android.os.SystemProperties; @@ -273,7 +272,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -304,7 +302,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -335,7 +332,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelManualOnly_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -365,7 +361,6 @@ public class RollbackPackageHealthObserverTest { @Test public void healthCheckFailed_impactLevelLowAndHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -404,7 +399,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLow_nativeCrash_rollback() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -438,7 +432,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLow_rollbackFailedPackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -483,7 +476,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLow_rollbackAll() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -530,7 +522,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLowAndHigh_rollbackLow() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -578,7 +569,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelHigh_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId2 = 2; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); @@ -612,7 +602,6 @@ public class RollbackPackageHealthObserverTest { */ @Test public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -637,7 +626,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -662,7 +650,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true)); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -692,7 +679,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelManualOnly_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -720,7 +706,6 @@ public class RollbackPackageHealthObserverTest { @Test public void onBootLoop_impactLevelLowAndHigh_onePackage() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, @@ -757,7 +742,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelLow_rollbackAll() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -802,7 +786,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -847,7 +830,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId2 = 2; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); @@ -882,7 +864,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelLowAndManual_rollbackLowImpactOnly() throws PackageManager.NameNotFoundException, InterruptedException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -928,7 +909,6 @@ public class RollbackPackageHealthObserverTest { @Test public void execute_impactLevelManual_rollbackLowImpactOnly() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); @@ -962,7 +942,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); int rollbackId1 = 1; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); @@ -1008,7 +987,6 @@ public class RollbackPackageHealthObserverTest { @Test public void executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh() throws PackageManager.NameNotFoundException { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true)); int rollbackId1 = 1; VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp index 8be74eaccd20..44e545bac0ce 100644 --- a/tests/PackageWatchdog/Android.bp +++ b/tests/PackageWatchdog/Android.bp @@ -26,12 +26,12 @@ android_test { name: "PackageWatchdogTest", srcs: ["src/**/*.java"], static_libs: [ - "junit", - "mockito-target-extended-minus-junit4", + "PlatformProperties", + "androidx.test.rules", "flag-junit", "frameworks-base-testutils", - "androidx.test.rules", - "PlatformProperties", + "junit", + "mockito-target-extended-minus-junit4", "services.core", "services.net", "truth", @@ -49,5 +49,9 @@ android_test { "libstaticjvmtiagent", ], platform_apis: true, - test_suites: ["device-tests"], + test_suites: [ + "device-tests", + "mts-crashrecovery", + ], + min_sdk_version: "36", } diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java index 4c16a756fbdc..c2ab0550ea05 100644 --- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java +++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java @@ -133,7 +133,6 @@ public class CrashRecoveryTest { @Before public void setUp() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); MockitoAnnotations.initMocks(this); new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); @@ -766,8 +765,6 @@ public class CrashRecoveryTest { watchdog.notifyPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); - if (Flags.recoverabilityDetection()) { - moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS); - } + moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS); } } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 1c50cb1b55fd..b8274841f65b 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -150,7 +150,6 @@ public class PackageWatchdogTest { @Before public void setUp() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); MockitoAnnotations.initMocks(this); new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); @@ -480,60 +479,6 @@ public class PackageWatchdogTest { assertThat(observer.mHealthCheckFailedPackages).isEmpty(); } - - /** - * Test package failure and notifies only least impact observers. - */ - @Test - public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { - mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); - PackageWatchdog watchdog = createWatchdog(); - TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); - TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); - TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); - TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); - - // Start observing for all impact observers - watchdog.registerHealthObserver(mTestExecutor, observerNone); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C, APP_D), - SHORT_DURATION, observerNone); - watchdog.registerHealthObserver(mTestExecutor, observerHigh); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C), SHORT_DURATION, - observerHigh); - watchdog.registerHealthObserver(mTestExecutor, observerMid); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B), SHORT_DURATION, - observerMid); - watchdog.registerHealthObserver(mTestExecutor, observerLow); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observerLow); - - // Then fail all apps above the threshold - raiseFatalFailureAndDispatch(watchdog, - Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), - new VersionedPackage(APP_B, VERSION_CODE), - new VersionedPackage(APP_C, VERSION_CODE), - new VersionedPackage(APP_D, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - - // Verify least impact observers are notifed of package failures - List<String> observerNonePackages = observerNone.mMitigatedPackages; - List<String> observerHighPackages = observerHigh.mMitigatedPackages; - List<String> observerMidPackages = observerMid.mMitigatedPackages; - List<String> observerLowPackages = observerLow.mMitigatedPackages; - - // APP_D failure observed by only observerNone is not caught cos its impact is none - assertThat(observerNonePackages).isEmpty(); - // APP_C failure is caught by observerHigh cos it's the lowest impact observer - assertThat(observerHighPackages).containsExactly(APP_C); - // APP_B failure is caught by observerMid cos it's the lowest impact observer - assertThat(observerMidPackages).containsExactly(APP_B); - // APP_A failure is caught by observerLow cos it's the lowest impact observer - assertThat(observerLowPackages).containsExactly(APP_A); - } - @Test public void testPackageFailureNotifyAllDifferentImpactsRecoverability() throws Exception { PackageWatchdog watchdog = createWatchdog(); @@ -583,84 +528,6 @@ public class PackageWatchdogTest { assertThat(observerLowPackages).containsExactly(APP_A); } - /** - * Test package failure and least impact observers are notified successively. - * State transistions: - * - * <ul> - * <li>(observer1:low, observer2:mid) -> {observer1} - * <li>(observer1:high, observer2:mid) -> {observer2} - * <li>(observer1:high, observer2:none) -> {observer1} - * <li>(observer1:none, observer2:none) -> {} - * <ul> - */ - @Test - public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception { - mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); - PackageWatchdog watchdog = createWatchdog(); - TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); - TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); - - // Start observing for observerFirst and observerSecond with failure handling - watchdog.registerHealthObserver(mTestExecutor, observerFirst); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerFirst); - watchdog.registerHealthObserver(mTestExecutor, observerSecond); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerSecond); - - // Then fail APP_A above the threshold - raiseFatalFailureAndDispatch(watchdog, - Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - - // Verify only observerFirst is notifed - assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A); - assertThat(observerSecond.mMitigatedPackages).isEmpty(); - - // After observerFirst handles failure, next action it has is high impact - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; - observerFirst.mMitigatedPackages.clear(); - observerSecond.mMitigatedPackages.clear(); - - // Then fail APP_A again above the threshold - raiseFatalFailureAndDispatch(watchdog, - Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - - // Verify only observerSecond is notifed cos it has least impact - assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A); - assertThat(observerFirst.mMitigatedPackages).isEmpty(); - - // After observerSecond handles failure, it has no further actions - observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - observerFirst.mMitigatedPackages.clear(); - observerSecond.mMitigatedPackages.clear(); - - // Then fail APP_A again above the threshold - raiseFatalFailureAndDispatch(watchdog, - Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - - // Verify only observerFirst is notifed cos it has the only action - assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A); - assertThat(observerSecond.mMitigatedPackages).isEmpty(); - - // After observerFirst handles failure, it too has no further actions - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - observerFirst.mMitigatedPackages.clear(); - observerSecond.mMitigatedPackages.clear(); - - // Then fail APP_A again above the threshold - raiseFatalFailureAndDispatch(watchdog, - Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - - // Verify no observer is notified cos no actions left - assertThat(observerFirst.mMitigatedPackages).isEmpty(); - assertThat(observerSecond.mMitigatedPackages).isEmpty(); - } - @Test public void testPackageFailureNotifyLeastImpactSuccessivelyRecoverability() throws Exception { PackageWatchdog watchdog = createWatchdog(); @@ -727,34 +594,6 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); } - /** - * Test package failure and notifies only one observer even with observer impact tie. - */ - @Test - public void testPackageFailureNotifyOneSameImpact() throws Exception { - mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); - PackageWatchdog watchdog = createWatchdog(); - TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); - TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); - - // Start observing for observer1 and observer2 with failure handling - watchdog.registerHealthObserver(mTestExecutor, observer2); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer2); - watchdog.registerHealthObserver(mTestExecutor, observer1); - watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1); - - // Then fail APP_A above the threshold - raiseFatalFailureAndDispatch(watchdog, - Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - - // Verify only one observer is notifed - assertThat(observer1.mMitigatedPackages).containsExactly(APP_A); - assertThat(observer2.mMitigatedPackages).isEmpty(); - } - @Test public void testPackageFailureNotifyOneSameImpactRecoverabilityDetection() throws Exception { PackageWatchdog watchdog = createWatchdog(); @@ -1015,27 +854,6 @@ public class PackageWatchdogTest { @Test @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY) - public void testNetworkStackFailure() { - mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); - final PackageWatchdog wd = createWatchdog(); - - // Start observing with failure handling - TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); - wd.startExplicitHealthCheck(Collections.singletonList(APP_A), SHORT_DURATION, observer); - - // Notify of NetworkStack failure - mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A); - - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); - - // Verify the NetworkStack observer is notified - assertThat(observer.mMitigatedPackages).containsExactly(APP_A); - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY) public void testNetworkStackFailureRecoverabilityDetection() { final PackageWatchdog wd = createWatchdog(); @@ -1270,21 +1088,6 @@ public class PackageWatchdogTest { assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty(); } - - /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */ - @Test - public void testBootLoopDetection_meetsThreshold() { - mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); - PackageWatchdog watchdog = createWatchdog(); - TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); - watchdog.registerHealthObserver(mTestExecutor, bootObserver); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { - watchdog.noteBoot(); - } - mTestLooper.dispatchAll(); - assertThat(bootObserver.mitigatedBootLoop()).isTrue(); - } - @Test public void testBootLoopDetection_meetsThresholdRecoverability() { PackageWatchdog watchdog = createWatchdog(); @@ -1330,27 +1133,6 @@ public class PackageWatchdogTest { assertThat(bootObserver.mitigatedBootLoop()).isFalse(); } - /** - * Ensure that boot loop mitigation is done for the observer with the lowest user impact - */ - @Test - public void testBootLoopMitigationDoneForLowestUserImpact() { - mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); - PackageWatchdog watchdog = createWatchdog(); - TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); - bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); - TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); - bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); - watchdog.registerHealthObserver(mTestExecutor, bootObserver1); - watchdog.registerHealthObserver(mTestExecutor, bootObserver2); - for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { - watchdog.noteBoot(); - } - mTestLooper.dispatchAll(); - assertThat(bootObserver1.mitigatedBootLoop()).isTrue(); - assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); - } - @Test public void testBootLoopMitigationDoneForLowestUserImpactRecoverability() { PackageWatchdog watchdog = createWatchdog(); @@ -1368,32 +1150,6 @@ public class PackageWatchdogTest { assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); } - /** - * Ensure that the correct mitigation counts are sent to the boot loop observer. - */ - @Test - public void testMultipleBootLoopMitigation() { - mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); - PackageWatchdog watchdog = createWatchdog(); - TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); - watchdog.registerHealthObserver(mTestExecutor, bootObserver); - for (int i = 0; i < 4; i++) { - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) { - watchdog.noteBoot(); - } - } - - moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); - - for (int i = 0; i < 4; i++) { - for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) { - watchdog.noteBoot(); - } - } - mTestLooper.dispatchAll(); - assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); - } - @Test public void testMultipleBootLoopMitigationRecoverabilityLowImpact() { PackageWatchdog watchdog = createWatchdog(); @@ -1800,9 +1556,7 @@ public class PackageWatchdogTest { watchdog.notifyPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); - if (Flags.recoverabilityDetection()) { - moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS); - } + moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS); } private PackageWatchdog createWatchdog() { |