Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 112eb8c1 authored by Chong Zhang's avatar Chong Zhang
Browse files

Save window when an app died while it's visible

- If an app died visible, keep the dead window and leave it on screen.

- Apply 50% dim over the dead window to indicate it's no longer active.

- Monitor touch inputs on the dead window and restart the app on tap.

bug: 24913379
Change-Id: I911da4e6135f2bffaf3b1bbe6f911ff689a278ff
parent 570b6bde
Loading
Loading
Loading
Loading
+36 −23
Original line number Diff line number Diff line
@@ -1456,6 +1456,12 @@ final class ActivityStack {
                    }

                    if (r.app == null || r.app.thread == null) {
                        // We need to make sure the app is running if it's the top, or it is
                        // just made visible from invisible.
                        // If the app is already visible, it must have died while it was visible.
                        // In this case, we'll show the dead window but will not restart the app.
                        // Otherwise we could end up thrashing.
                        if (r == top || !r.visible) {
                            // This activity needs to be visible, but isn't even running...
                            // get it started and resume if no other stack in this stack is resumed.
                            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
@@ -1478,7 +1484,7 @@ final class ActivityStack {
                                    noStackActivityResumed = false;
                                }
                            }

                        }
                    } else if (r.visible) {
                        // If this activity is already visible, then there is nothing
                        // else to do here.
@@ -3731,10 +3737,13 @@ final class ActivityStack {
                        // Don't currently have state for the activity, or
                        // it is finishing -- always remove it.
                        remove = true;
                    } else if (r.launchCount > 2 &&
                    } else if (!r.visible && r.launchCount > 2 &&
                            r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
                        // We have launched this activity too many times since it was
                        // able to run, so give up and remove it.
                        // (Note if the activity is visible, we don't remove the record.
                        // We leave the dead window on the screen but the process will
                        // not be restarted unless user explicitly tap on it.)
                        remove = true;
                    } else {
                        // The process may be gone, but the activity lives on!
@@ -3764,7 +3773,11 @@ final class ActivityStack {
                        if (DEBUG_APP) Slog.v(TAG_APP,
                                "Clearing app during removeHistory for activity " + r);
                        r.app = null;
                        r.nowVisible = false;
                        // Set nowVisible to previous visible state. If the app was visible while
                        // it died, we leave the dead window on screen so it's basically visible.
                        // This is needed when user later tap on the dead window, we need to stop
                        // other apps when user transfers focus to the restarted activity.
                        r.nowVisible = r.visible;
                        if (!r.haveState) {
                            if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE,
                                    "App died, clearing saved state of " + r);
+9 −9
Original line number Diff line number Diff line
@@ -2872,24 +2872,24 @@ public final class ActivityStackSupervisor implements DisplayListener {
            return;
        }

        int stackId = task.stack.mStackId;
        if (task.mResizeable && options != null) {
            ActivityOptions opts = new ActivityOptions(options);
            if (opts.hasBounds()) {
                Rect bounds = opts.getBounds();
                task.updateOverrideConfiguration(bounds);
                mWindowManager.resizeTask(task.taskId, bounds, task.mOverrideConfig,
                        false /*relayout*/, false /*forced*/);
                stackId = task.getLaunchStackId();
            }
        }

                final int stackId = task.getLaunchStackId();
                if (stackId != task.stack.mStackId) {
                    moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);

                    // moveTaskToStackUncheckedLocked() should already placed the task on top,
                    // still need moveTaskToFrontLocked() below for any transition settings.
                }
                // WM resizeTask must be done after the task is moved to the correct stack,
                // because Task's setBounds() also updates dim layer's bounds, but that has
                // dependency on the stack.
                mWindowManager.resizeTask(task.taskId, bounds, task.mOverrideConfig,
                        false /*relayout*/, false /*forced*/);
            }
        }

        final ActivityRecord r = task.getTopActivity();
        task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
+21 −0
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ class AppWindowToken extends WindowToken {
    // Set to true when the token has been removed from the window mgr.
    boolean removed;

    boolean appDied;
    // Information about an application starting window if displayed.
    StartingData startingData;
    WindowState startingWindow;
@@ -365,6 +366,26 @@ class AppWindowToken extends WindowToken {
        windows.clear();
    }

    void removeAllDeadWindows() {
        for (int winNdx = allAppWindows.size() - 1; winNdx >= 0;
                // removeWindowLocked at bottom of loop may remove multiple entries from
                // allAppWindows if the window to be removed has child windows. It also may
                // not remove any windows from allAppWindows at all if win is exiting and
                // currently animating away. This ensures that winNdx is monotonically decreasing
                // and never beyond allAppWindows bounds.
                winNdx = Math.min(winNdx - 1, allAppWindows.size() - 1)) {
            WindowState win = allAppWindows.get(winNdx);
            if (win.mAppDied) {
                if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) {
                    Slog.w(WindowManagerService.TAG, "removeAllDeadWindows: " + win);
                }
                // Set mDestroying, we don't want any animation or delayed removal here.
                win.mDestroying = true;
                service.removeWindowLocked(win);
            }
        }
    }

    @Override
    void dump(PrintWriter pw, String prefix) {
        super.dump(pw, prefix);
+59 −31
Original line number Diff line number Diff line
package com.android.server.wm;

import static com.android.server.wm.WindowManagerService.DEBUG_DIM_LAYER;
import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;

import android.graphics.Rect;
import android.util.ArrayMap;
@@ -11,32 +12,38 @@ import java.io.PrintWriter;

/**
 * Centralizes the control of dim layers used for
 * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}.
 * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
 * as well as other use cases (such as dimming above a dead window).
 */
class DimBehindController {
    private static final String TAG = "DimBehindController";
class DimLayerController {
    private static final String TAG = "DimLayerController";

    /** Amount of time in milliseconds to animate the dim surface from one value to another,
     * when no window animation is driving it. */
    private static final int DEFAULT_DIM_DURATION = 200;

    // Shared dim layer for fullscreen users. {@link DimBehindState#dimLayer} will point to this
    /**
     * The default amount of dim applied over a dead window
     */
    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;

    // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
    // instead of creating a new object per fullscreen task on a display.
    private DimLayer mSharedFullScreenDimLayer;

    private ArrayMap<DimLayer.DimLayerUser, DimBehindState> mState = new ArrayMap<>();
    private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();

    private DisplayContent mDisplayContent;

    private Rect mTmpBounds = new Rect();

    DimBehindController(DisplayContent displayContent) {
    DimLayerController(DisplayContent displayContent) {
        mDisplayContent = displayContent;
    }

    /** Updates the dim layer bounds, recreating it if needed. */
    void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
        DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, false);
        final boolean previousFullscreen = state.dimLayer != null
                && state.dimLayer == mSharedFullScreenDimLayer;
        DimLayer newDimLayer;
@@ -72,19 +79,21 @@ class DimBehindController {
        state.dimLayer = newDimLayer;
    }

    private DimBehindState getOrCreateDimBehindState(DimLayer.DimLayerUser dimLayerUser) {
        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getDimBehindState, dimLayerUser="
    private DimLayerState getOrCreateDimLayerState(
            DimLayer.DimLayerUser dimLayerUser, boolean aboveApp) {
        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
                + dimLayerUser.toShortString());
        DimBehindState state = mState.get(dimLayerUser);
        DimLayerState state = mState.get(dimLayerUser);
        if (state == null) {
            state = new DimBehindState();
            state = new DimLayerState();
            mState.put(dimLayerUser, state);
        }
        state.dimAbove = aboveApp;
        return state;
    }

    private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
        DimBehindState state = mState.get(dimLayerUser);
        DimLayerState state = mState.get(dimLayerUser);
        if (state == null) {
            if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
                    + dimLayerUser.toShortString());
@@ -95,7 +104,7 @@ class DimBehindController {

    boolean isDimming() {
        for (int i = mState.size() - 1; i >= 0; i--) {
            DimBehindState state = mState.valueAt(i);
            DimLayerState state = mState.valueAt(i);
            if (state.dimLayer != null && state.dimLayer.isDimming()) {
                return true;
            }
@@ -110,15 +119,15 @@ class DimBehindController {
    }

    private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
        DimBehindState state = mState.get(dimLayerUser);
        DimLayerState state = mState.get(dimLayerUser);
        return state != null && state.continueDimming;
    }

    void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
            WindowStateAnimator newWinAnimator) {
            WindowStateAnimator newWinAnimator, boolean aboveApp) {
        // Only set dim params on the highest dimmed layer.
        // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
        DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, aboveApp);
        if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
                + " dimLayerUser=" + dimLayerUser.toShortString()
                + " newWinAnimator=" + newWinAnimator
@@ -145,7 +154,7 @@ class DimBehindController {

    private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
        // No need to check if state is null, we know the key has a value.
        DimBehindState state = mState.get(dimLayerUser);
        DimLayerState state = mState.get(dimLayerUser);
        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
                + " dimLayerUser=" + dimLayerUser.toShortString()
                + " state.continueDimming=" + state.continueDimming
@@ -188,7 +197,7 @@ class DimBehindController {
    }

    private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
        DimBehindState state = mState.get(dimLayerUser);
        DimLayerState state = mState.get(dimLayerUser);
        if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
                + " dimLayerUser=" + dimLayerUser.toShortString()
                + " state.animator=" + state.animator
@@ -199,9 +208,14 @@ class DimBehindController {
            dimLayer = state.dimLayer.getLayer();
            dimAmount = 0;
        } else {
            dimLayer = state.animator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
            if (state.dimAbove) {
                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
            } else {
                dimLayer = state.animator.mAnimLayer - LAYER_OFFSET_DIM;
                dimAmount = state.animator.mWin.mAttrs.dimAmount;
            }
        }
        final float targetAlpha = state.dimLayer.getTargetAlpha();
        if (targetAlpha != dimAmount) {
            if (state.animator == null) {
@@ -211,7 +225,7 @@ class DimBehindController {
                        ? state.animator.mAnimation.computeDurationHint()
                        : DEFAULT_DIM_DURATION;
                if (targetAlpha > dimAmount) {
                    duration = getDimBehindFadeDuration(duration);
                    duration = getDimLayerFadeDuration(duration);
                }
                state.dimLayer.show(dimLayer, dimAmount, duration);
            }
@@ -230,11 +244,11 @@ class DimBehindController {
    }

    boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
        DimBehindState state = mState.get(dimLayerUser);
        DimLayerState state = mState.get(dimLayerUser);
        return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
    }

    private long getDimBehindFadeDuration(long duration) {
    private long getDimLayerFadeDuration(long duration) {
        TypedValue tv = new TypedValue();
        mDisplayContent.mService.mContext.getResources().getValue(
                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
@@ -248,7 +262,7 @@ class DimBehindController {

    void close() {
        for (int i = mState.size() - 1; i >= 0; i--) {
            DimBehindState state = mState.valueAt(i);
            DimLayerState state = mState.valueAt(i);
            state.dimLayer.destroySurface();
        }
        mState.clear();
@@ -256,10 +270,23 @@ class DimBehindController {
    }

    void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
        DimLayerState state = mState.get(dimLayerUser);
        if (state != null) {
            state.dimLayer.destroySurface();
            mState.remove(dimLayerUser);
        }
    }

    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
        applyDim(dimLayerUser, animator, false /* aboveApp */);
    }

    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
        applyDim(dimLayerUser, animator, true /* aboveApp */);
    }

    private void applyDim(
            DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
        if (dimLayerUser == null) {
            Slog.e(TAG, "Trying to apply dim layer for: " + this
                    + ", but no dim layer user found.");
@@ -269,26 +296,27 @@ class DimBehindController {
            setContinueDimming(dimLayerUser);
            if (!isDimming(dimLayerUser, animator)) {
                if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
                startDimmingIfNeeded(dimLayerUser, animator);
                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
            }
        }
    }

    private static class DimBehindState {
        // The particular window with FLAG_DIM_BEHIND set. If null, hide dimLayer.
    private static class DimLayerState {
        // The particular window requesting a dim layer. If null, hide dimLayer.
        WindowStateAnimator animator;
        // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
        // end then stop any dimming.
        boolean continueDimming;
        DimLayer dimLayer;
        boolean dimAbove;
    }

    void dump(String prefix, PrintWriter pw) {
        pw.println(prefix + "DimBehindController");
        pw.println(prefix + "DimLayerController");
        for (int i = 0, n = mState.size(); i < n; i++) {
            pw.println(prefix + "  " + mState.keyAt(i).toShortString());
            pw.print(prefix + "    ");
            DimBehindState state = mState.valueAt(i);
            DimLayerState state = mState.valueAt(i);
            pw.print("dimLayer=" + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" :
                    state.dimLayer));
            pw.print(", animator=" + state.animator);
+9 −9
Original line number Diff line number Diff line
@@ -116,7 +116,7 @@ class DisplayContent {

    final DockedStackDividerController mDividerControllerLocked;

    final DimBehindController mDimBehindController;
    final DimLayerController mDimLayerController;

    /**
     * @param display May not be null.
@@ -131,7 +131,7 @@ class DisplayContent {
        mService = service;
        initializeDisplayBaseInfo();
        mDividerControllerLocked = new DockedStackDividerController(service.mContext, this);
        mDimBehindController = new DimBehindController(this);
        mDimLayerController = new DimLayerController(this);
    }

    int getDisplayId() {
@@ -271,7 +271,7 @@ class DisplayContent {
    }

    void detachStack(TaskStack stack) {
        mDimBehindController.removeDimLayerUser(stack);
        mDimLayerController.removeDimLayerUser(stack);
        mStacks.remove(stack);
    }

@@ -415,23 +415,23 @@ class DisplayContent {
    }

    boolean animateDimLayers() {
        return mDimBehindController.animateDimLayers();
        return mDimLayerController.animateDimLayers();
    }

    void resetDimming() {
        mDimBehindController.resetDimming();
        mDimLayerController.resetDimming();
    }

    boolean isDimming() {
        return mDimBehindController.isDimming();
        return mDimLayerController.isDimming();
    }

    void stopDimmingIfNeeded() {
        mDimBehindController.stopDimmingIfNeeded();
        mDimLayerController.stopDimmingIfNeeded();
    }

    void close() {
        mDimBehindController.close();
        mDimLayerController.close();
        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
            mStacks.get(stackNdx).close();
        }
@@ -578,7 +578,7 @@ class DisplayContent {
            }
        }
        pw.println();
        mDimBehindController.dump(prefix + "  ", pw);
        mDimLayerController.dump(prefix + "  ", pw);
    }

    @Override
Loading