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

Commit 03fea28b authored by Jorge Gil's avatar Jorge Gil
Browse files

Update AppHandle visibility for keyguard/immersive cases

Creates a window decoration for a fullscreen task even when the keyguard
is showing and occluding to fix a race between the keyguard change
callback and #shouldShowWindowDecor. Now, a decoration will be created
always and shown or hidden when the keyguard state changes.
This ensures the App Handle is visible in tasks launched from Hub when
dreaming, even if the keyguard listener calls back after the decoration
is created. It also keeps the existing behavior of not showing the App
Handle when an app is opened over the keyguard and occluding.

Also refactors the caption bar visibility state in WindowDecoration to
account for both keyguard and immersive states at the same time,
including checking that the inset state changes only apply to the tasks
in that same display.

Bug: 355614330
Test: launch app from Hub when dreaming - check App Handle is visible
Test: enter/exit Youtube immersive mode should hide/show the App Handle,
with enableAdditionalWindowsAboveStatusBar flag enabled and disabled
Test: open Calculator (or app that shows over keyguard) in fullscreen,
lock and unlock using the power button - check there's no App Handle
until since the keyguard is still locked behind the Calculator
Flag: EXEMPT bugfix

Change-Id: Ib517722472b2ce04c9af4a4ea87e84423ca374cb
parent 0d20e8d9
Loading
Loading
Loading
Loading
+34 −3
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
    private final SparseArray<PerDisplay> mInsetsPerDisplay = new SparseArray<>();
    private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
            new SparseArray<>();
    private final CopyOnWriteArrayList<OnInsetsChangedListener> mGlobalListeners =
            new CopyOnWriteArrayList<>();

    public DisplayInsetsController(IWindowManager wmService,
            ShellInit shellInit,
@@ -80,6 +82,16 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
        }
    }

    /**
     * Adds a callback to listen for insets changes for any display. Note that the
     * listener will not be updated with the existing state of the insets on any display.
     */
    public void addGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
        if (!mGlobalListeners.contains(listener)) {
            mGlobalListeners.add(listener);
        }
    }

    /**
     * Removes a callback listening for insets changes from a particular display.
     */
@@ -91,6 +103,13 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
        listeners.remove(listener);
    }

    /**
     * Removes a callback listening for insets changes from any display.
     */
    public void removeGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
        mGlobalListeners.remove(listener);
    }

    @Override
    public void onDisplayAdded(int displayId) {
        PerDisplay pd = new PerDisplay(displayId);
@@ -138,12 +157,17 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan

        private void insetsChanged(InsetsState insetsState) {
            CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
            if (listeners == null) {
            if (listeners == null && mGlobalListeners.isEmpty()) {
                return;
            }
            mDisplayController.updateDisplayInsets(mDisplayId, insetsState);
            for (OnInsetsChangedListener listener : mGlobalListeners) {
                listener.insetsChanged(mDisplayId, insetsState);
            }
            if (listeners != null) {
                for (OnInsetsChangedListener listener : listeners) {
                listener.insetsChanged(insetsState);
                    listener.insetsChanged(mDisplayId, insetsState);
                }
            }
        }

@@ -284,6 +308,13 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
         */
        default void insetsChanged(InsetsState insetsState) {}

        /**
         * Called when the window insets configuration has changed for the given display.
         */
        default void insetsChanged(int displayId, InsetsState insetsState) {
            insetsChanged(insetsState);
        }

        /**
         * Called when this window retrieved control over a specified set of insets sources.
         */
+26 −34
Original line number Diff line number Diff line
@@ -69,7 +69,6 @@ import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -115,6 +114,7 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;

@@ -321,7 +321,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    private void onInit() {
        mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
        mShellCommandHandler.addDumpCallback(this::dump, this);
        mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
        mDisplayInsetsController.addGlobalInsetsChangedListener(
                new DesktopModeOnInsetsChangedListener());
        mDesktopTasksController.setOnTaskResizeAnimationListener(
                new DesktopModeOnTaskResizeAnimationListener());
@@ -1196,10 +1196,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
            return false;
        }
        if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded()
                && taskInfo.isFocused) {
            return false;
        }
        if (DesktopModeFlags.MODALS_POLICY.isEnabled(mContext)
                && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
            return false;
@@ -1397,19 +1393,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        }
    }

    static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
        private boolean mIsKeyguardVisible;
        private boolean mIsKeyguardOccluded;

    class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
        @Override
        public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
                boolean animatingDismiss) {
            mIsKeyguardVisible = visible;
            mIsKeyguardOccluded = occluded;
            final int size = mWindowDecorByTaskId.size();
            for (int i = size - 1; i >= 0; i--) {
                final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
                if (decor != null) {
                    decor.onKeyguardStateChanged(visible, occluded);
                }
            }

        public boolean isKeyguardVisibleAndOccluded() {
            return mIsKeyguardVisible && mIsKeyguardOccluded;
        }
    }

@@ -1417,28 +1411,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    class DesktopModeOnInsetsChangedListener implements
            DisplayInsetsController.OnInsetsChangedListener {
        @Override
        public void insetsChanged(InsetsState insetsState) {
            for (int i = 0; i < insetsState.sourceSize(); i++) {
                final InsetsSource source = insetsState.sourceAt(i);
                if (source.getType() != statusBars()) {
                    continue;
                }

                final DesktopModeWindowDecoration decor = getFocusedDecor();
        public void insetsChanged(int displayId, @NonNull InsetsState insetsState) {
            final int size = mWindowDecorByTaskId.size();
            for (int i = size - 1; i >= 0; i--) {
                final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
                if (decor == null) {
                    return;
                    continue;
                }
                // If status bar inset is visible, top task is not in immersive mode
                final boolean inImmersiveMode = !source.isVisible();
                // Calls WindowDecoration#relayout if decoration visibility needs to be updated
                if (inImmersiveMode != mInImmersiveMode) {
                    if (Flags.enableDesktopWindowingImmersiveHandleHiding()) {
                        decor.relayout(decor.mTaskInfo);
                if (decor.mTaskInfo.displayId == displayId
                        && Flags.enableDesktopWindowingImmersiveHandleHiding()) {
                    decor.onInsetsStateChanged(insetsState);
                }
                    mInImmersiveMode = inImmersiveMode;
                if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
                    // If status bar inset is visible, top task is not in immersive mode.
                    // This value is only needed when the App Handle input is being handled
                    // through the global input monitor (hence the flag check) to ignore gestures
                    // when the app is in immersive mode. When disabled, the view itself handles
                    // input, and since it's removed when in immersive there's no need to track
                    // this here.
                    mInImmersiveMode = !InsetsStateKt.isVisible(insetsState, statusBars());
                }

                return;
            }
        }
    }
+37 −18
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;

import java.util.ArrayList;
import java.util.Arrays;
@@ -143,6 +144,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
    TaskDragResizer mTaskDragResizer;
    boolean mIsCaptionVisible;

    private boolean mIsStatusBarVisible;
    private boolean mIsKeyguardVisibleAndOccluded;

    /** The most recent set of insets applied to this window decoration. */
    private WindowDecorationInsets mWindowDecorationInsets;
    private final Binder mOwner = new Binder();
@@ -184,6 +188,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
        mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
        mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
        final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
        mIsStatusBarVisible = insetsState != null
                && InsetsStateKt.isVisible(insetsState, statusBars());
    }

    /**
@@ -234,7 +241,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        }
        rootView = null; // Clear it just in case we use it accidentally

        updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
        updateCaptionVisibility(outResult.mRootView);

        final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
        outResult.mWidth = taskBounds.width();
@@ -284,17 +291,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
            mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
            mDecorWindowContext.setTheme(mContext.getThemeResId());
            if (params.mLayoutResId != 0) {
                outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
                        .inflate(params.mLayoutResId, null);
                outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
            }
        }

        if (outResult.mRootView == null) {
            outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
                    .inflate(params.mLayoutResId, null);
            outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
        }
    }

    @VisibleForTesting
    T inflateLayout(Context context, int layoutResId) {
        return (T) LayoutInflater.from(context).inflate(layoutResId, null);
    }

    private void updateDecorationContainerSurface(
            SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
        if (mDecorationContainerSurface == null) {
@@ -497,24 +507,33 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
    }

    /**
     * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
     */
    private void updateCaptionVisibility(View rootView, int displayId) {
        final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
        for (int i = 0; i < insetsState.sourceSize(); i++) {
            final InsetsSource source = insetsState.sourceAt(i);
            if (source.getType() != statusBars()) {
                continue;
    void onKeyguardStateChanged(boolean visible, boolean occluded) {
        final boolean prevVisAndOccluded = mIsKeyguardVisibleAndOccluded;
        mIsKeyguardVisibleAndOccluded = visible && occluded;
        final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded;
        if (changed) {
            relayout(mTaskInfo);
        }
    }

            mIsCaptionVisible = source.isVisible();
            setCaptionVisibility(rootView, mIsCaptionVisible);
    void onInsetsStateChanged(@NonNull InsetsState insetsState) {
        final boolean prevStatusBarVisibility = mIsStatusBarVisible;
        mIsStatusBarVisible = InsetsStateKt.isVisible(insetsState, statusBars());
        final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible;

            return;
        if (changed) {
            relayout(mTaskInfo);
        }
    }

    /**
     * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
     */
    private void updateCaptionVisibility(View rootView) {
        mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
        setCaptionVisibility(rootView, mIsCaptionVisible);
    }

    void setTaskDragResizer(TaskDragResizer taskDragResizer) {
        mTaskDragResizer = taskDragResizer;
    }
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.wm.shell.windowdecor.extension

import android.view.InsetsState
import android.view.WindowInsets

/**
 * Whether the source of the given [type] is visible or false if there is no source of that type.
 */
fun InsetsState.isVisible(@WindowInsets.Type.InsetsType type: Int): Boolean {
    for (i in 0 until sourceSize()) {
        val source = sourceAt(i)
        if (source.type != type) {
            continue
        }
        return source.isVisible
    }
    return false
}
+23 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2024 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/caption"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="end"
    android:background="@drawable/caption_decor_title"/>
 No newline at end of file
Loading