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

Commit 6120526f authored by Shivangi Dubey's avatar Shivangi Dubey Committed by Android (Google) Code Review
Browse files

Merge "Create observer flow for Caption handle state changes" into main

parents 5d1637c9 106c3972
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
@@ -141,7 +142,7 @@ import java.util.Optional;
        includes = {
                WMShellBaseModule.class,
                PipModule.class,
                ShellBackAnimationModule.class,
                ShellBackAnimationModule.class
        })
public abstract class WMShellModule {

@@ -247,6 +248,7 @@ public abstract class WMShellModule {
            AssistContentRequester assistContentRequester,
            MultiInstanceHelper multiInstanceHelper,
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
            Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
        if (DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -272,6 +274,7 @@ public abstract class WMShellModule {
                    assistContentRequester,
                    multiInstanceHelper,
                    desktopTasksLimiter,
                    windowDecorCaptionHandleRepository,
                    desktopActivityOrientationHandler,
                    windowDecorViewHostSupplier);
        }
@@ -778,6 +781,12 @@ public abstract class WMShellModule {
        return new AppHandleEducationFilter(context, appHandleEducationDatastoreRepository);
    }

    @WMSingleton
    @Provides
    static WindowDecorCaptionHandleRepository provideAppHandleRepository() {
        return new WindowDecorCaptionHandleRepository();
    }

    @WMSingleton
    @Provides
    static AppHandleEducationController provideAppHandleEducationController(
+58 −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.desktopmode

import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Rect
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

/** Repository to observe caption state. */
class WindowDecorCaptionHandleRepository {
  private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
  /** Observer for app handle state changes. */
  val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow

  /** Notifies [captionStateFlow] if there is a change to caption state. */
  fun notifyCaptionChanged(captionState: CaptionState) {
    _captionStateFlow.value = captionState
  }
}

/**
 * Represents the current status of the caption.
 *
 * It can be one of three options:
 * * [AppHandle]: Indicating that there is at least one visible app handle on the screen.
 * * [AppHeader]: Indicating that there is at least one visible app chip on the screen.
 * * [NoCaption]: Signifying that no caption handle is currently visible on the device.
 */
sealed class CaptionState {
  data class AppHandle(
      val runningTaskInfo: RunningTaskInfo,
      val isHandleMenuExpanded: Boolean,
      val globalAppHandleBounds: Rect
  ) : CaptionState()

  data class AppHeader(
      val runningTaskInfo: RunningTaskInfo,
      val isHeaderMenuExpanded: Boolean,
      val globalAppChipBounds: Rect
  ) : CaptionState()

  data object NoCaption : CaptionState()
}
+12 −0
Original line number Diff line number Diff line
@@ -109,6 +109,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -164,7 +165,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    private final InputManager mInputManager;
    private final InteractionJankMonitor mInteractionJankMonitor;
    private final MultiInstanceHelper mMultiInstanceHelper;
    private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
    private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter;
    private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
    private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier;
    private boolean mTransitionDragActive;

@@ -234,6 +237,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            AssistContentRequester assistContentRequester,
            MultiInstanceHelper multiInstanceHelper,
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
        this(
@@ -259,10 +263,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                new DesktopModeWindowDecoration.Factory(),
                new InputMonitorFactory(),
                SurfaceControl.Transaction::new,
                new AppHeaderViewHolder.Factory(),
                rootTaskDisplayAreaOrganizer,
                new SparseArray<>(),
                interactionJankMonitor,
                desktopTasksLimiter,
                windowDecorCaptionHandleRepository,
                activityOrientationChangeHandler,
                new TaskPositionerFactory());
    }
@@ -291,10 +297,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
            InputMonitorFactory inputMonitorFactory,
            Supplier<SurfaceControl.Transaction> transactionFactory,
            AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
            InteractionJankMonitor interactionJankMonitor,
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
            TaskPositionerFactory taskPositionerFactory) {
        mContext = context;
@@ -317,6 +325,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
        mInputMonitorFactory = inputMonitorFactory;
        mTransactionFactory = transactionFactory;
        mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
        mGenericLinksParser = genericLinksParser;
        mInputManager = mContext.getSystemService(InputManager.class);
@@ -325,6 +334,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                com.android.internal.R.string.config_systemUi);
        mInteractionJankMonitor = interactionJankMonitor;
        mDesktopTasksLimiter = desktopTasksLimiter;
        mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
        mActivityOrientationChangeHandler = activityOrientationChangeHandler;
        mAssistContentRequester = assistContentRequester;
        mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
@@ -1377,10 +1387,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                        mBgExecutor,
                        mMainChoreographer,
                        mSyncQueue,
                        mAppHeaderViewHolderFactory,
                        mRootTaskDisplayAreaOrganizer,
                        mGenericLinksParser,
                        mAssistContentRequester,
                        mMultiInstanceHelper,
                        mWindowDecorCaptionHandleRepository,
                        mWindowDecorViewHostSupplier);
        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);

+99 −5
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_
import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;

import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode;
import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
@@ -85,6 +86,8 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -165,6 +168,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin

    private ExclusionRegionListener mExclusionRegionListener;

    private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    private final MaximizeMenuFactory mMaximizeMenuFactory;
    private final HandleMenuFactory mHandleMenuFactory;
@@ -181,6 +185,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
    private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
    private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
    private final MultiInstanceHelper mMultiInstanceHelper;
    private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;

    DesktopModeWindowDecoration(
            Context context,
@@ -194,20 +199,24 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            @ShellBackgroundThread ShellExecutor bgExecutor,
            Choreographer choreographer,
            SyncTransactionQueue syncQueue,
            AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            AppToWebGenericLinksParser genericLinksParser,
            AssistContentRequester assistContentRequester,
            MultiInstanceHelper multiInstanceHelper,
            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
        this (context, userContext, displayController, splitScreenController, taskOrganizer,
                taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
                rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
                appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
                assistContentRequester,
                SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
                        context.getSystemService(WindowManager.class)),
                new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier,
                DefaultMaximizeMenuFactory.INSTANCE,
                DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper);
                DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
                windowDecorCaptionHandleRepository);
    }

    DesktopModeWindowDecoration(
@@ -222,6 +231,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            @ShellBackgroundThread ShellExecutor bgExecutor,
            Choreographer choreographer,
            SyncTransactionQueue syncQueue,
            AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            AppToWebGenericLinksParser genericLinksParser,
            AssistContentRequester assistContentRequester,
@@ -234,7 +244,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            WindowDecorViewHostSupplier windowDecorViewHostSupplier,
            MaximizeMenuFactory maximizeMenuFactory,
            HandleMenuFactory handleMenuFactory,
            MultiInstanceHelper multiInstanceHelper) {
            MultiInstanceHelper multiInstanceHelper,
            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
        super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
                surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -244,6 +255,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        mBgExecutor = bgExecutor;
        mChoreographer = choreographer;
        mSyncQueue = syncQueue;
        mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
        mGenericLinksParser = genericLinksParser;
        mAssistContentRequester = assistContentRequester;
@@ -251,6 +263,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        mHandleMenuFactory = handleMenuFactory;
        mMultiInstanceHelper = multiInstanceHelper;
        mWindowManagerWrapper = windowManagerWrapper;
        mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
    }

    /**
@@ -383,6 +396,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        if (mResult.mRootView == null) {
            // This means something blocks the window decor from showing, e.g. the task is hidden.
            // Nothing is set up in this case including the decoration surface.
            if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
                notifyNoCaptionHandle();
            }
            disposeStatusBarInputLayer();
            Trace.endSection(); // DesktopModeWindowDecoration#relayout
            return;
@@ -398,6 +414,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            position.set(determineHandlePosition());
        }
        Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData");
        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
            notifyCaptionStateChanged();
        }
        mWindowDecorViewHolder.bindData(mTaskInfo,
                position,
                mResult.mCaptionWidth,
@@ -507,6 +526,67 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        return taskInfo.isFreeform() && taskInfo.isResizeable;
    }

    private void notifyCaptionStateChanged() {
        // TODO: b/366159408 - Ensure bounds sent with notification account for RTL mode.
        if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
            return;
        }
        if (!isCaptionVisible()) {
            notifyNoCaptionHandle();
        } else if (isAppHandle(mWindowDecorViewHolder)) {
            // App handle is visible since `mWindowDecorViewHolder` is of type
            // [AppHandleViewHolder].
            final CaptionState captionState = new CaptionState.AppHandle(mTaskInfo,
                    isHandleMenuActive(), getCurrentAppHandleBounds());
            mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
        } else {
            // App header is visible since `mWindowDecorViewHolder` is of type
            // [AppHeaderViewHolder].
            ((AppHeaderViewHolder) mWindowDecorViewHolder).runOnAppChipGlobalLayout(
                    () -> {
                        notifyAppChipStateChanged();
                        return Unit.INSTANCE;
                    });
        }
    }

    private void notifyNoCaptionHandle() {
        if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
            return;
        }
        mWindowDecorCaptionHandleRepository.notifyCaptionChanged(
                CaptionState.NoCaption.INSTANCE);
    }

    private Rect getCurrentAppHandleBounds() {
        return new Rect(
                mResult.mCaptionX,
                /* top= */0,
                mResult.mCaptionX + mResult.mCaptionWidth,
                mResult.mCaptionHeight);
    }

    private void notifyAppChipStateChanged() {
        final Rect appChipPositionInWindow =
                ((AppHeaderViewHolder) mWindowDecorViewHolder).getAppChipLocationInWindow();
        final Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
        final Rect appChipGlobalPosition = new Rect(
                taskBounds.left + appChipPositionInWindow.left,
                taskBounds.top + appChipPositionInWindow.top,
                taskBounds.left + appChipPositionInWindow.right,
                taskBounds.top + appChipPositionInWindow.bottom);
        final CaptionState captionState = new CaptionState.AppHeader(
                mTaskInfo,
                isHandleMenuActive(),
                appChipGlobalPosition);

        mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
    }

    private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
        return taskInfo.isFreeform() && taskInfo.isResizeable;
    }

    private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
        if (!isDragResizable(mTaskInfo, mContext) || !isMaximizeMenuActive()) {
            return;
@@ -556,7 +636,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        } else if (mRelayoutParams.mLayoutResId
                == R.layout.desktop_mode_app_header) {
            loadAppInfoIfNeeded();
            return new AppHeaderViewHolder(
            return mAppHeaderViewHolderFactory.create(
                    mResult.mRootView,
                    mOnCaptionTouchListener,
                    mOnCaptionButtonClickListener,
@@ -994,7 +1074,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                mAppIconBitmap,
                mAppName,
                mSplitScreenController,
                DesktopModeStatus.canEnterDesktopMode(mContext),
                canEnterDesktopMode(mContext),
                supportsMultiInstance,
                shouldShowManageWindowsButton,
                getBrowserLink(),
@@ -1027,6 +1107,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                    return Unit.INSTANCE;
                }
        );
        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
            notifyCaptionStateChanged();
        }
        mMinimumInstancesFound = false;
    }

@@ -1089,6 +1172,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        mWindowDecorViewHolder.onHandleMenuClosed();
        mHandleMenu.close();
        mHandleMenu = null;
        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
            notifyCaptionStateChanged();
        }
    }

    @Override
@@ -1260,6 +1346,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
        disposeResizeVeil();
        disposeStatusBarInputLayer();
        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
            notifyNoCaptionHandle();
        }

        super.close();
    }

@@ -1367,10 +1457,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                @ShellBackgroundThread ShellExecutor bgExecutor,
                Choreographer choreographer,
                SyncTransactionQueue syncQueue,
                AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
                RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
                AppToWebGenericLinksParser genericLinksParser,
                AssistContentRequester assistContentRequester,
                MultiInstanceHelper multiInstanceHelper,
                WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
                WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
            return new DesktopModeWindowDecoration(
                    context,
@@ -1384,10 +1476,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                    bgExecutor,
                    choreographer,
                    syncQueue,
                    appHeaderViewHolderFactory,
                    rootTaskDisplayAreaOrganizer,
                    genericLinksParser,
                    assistContentRequester,
                    multiInstanceHelper,
                    windowDecorCaptionHandleRepository,
                    windowDecorViewHostSupplier);
        }
    }
+53 −1
Original line number Diff line number Diff line
@@ -22,12 +22,14 @@ import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.view.View
import android.view.View.OnLongClickListener
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
@@ -62,7 +64,7 @@ import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppeara
 * finer controls such as a close window button and an "app info" section to pull up additional
 * controls.
 */
internal class AppHeaderViewHolder(
class AppHeaderViewHolder(
        rootView: View,
        onCaptionTouchListener: View.OnTouchListener,
        onCaptionButtonClickListener: View.OnClickListener,
@@ -279,6 +281,34 @@ internal class AppHeaderViewHolder(
        maximizeButtonView.startHoverAnimation()
    }

    fun runOnAppChipGlobalLayout(runnable: () -> Unit) {
        if (openMenuButton.isAttachedToWindow) {
            // App chip is already inflated.
            runnable()
            return
        }
        // Wait for app chip to be inflated before notifying repository.
        openMenuButton.viewTreeObserver.addOnGlobalLayoutListener(object :
            OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                runnable()
                openMenuButton.viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        })
    }

    fun getAppChipLocationInWindow(): Rect {
        val appChipBoundsInWindow = IntArray(2)
        openMenuButton.getLocationInWindow(appChipBoundsInWindow)

        return Rect(
            /* left = */ appChipBoundsInWindow[0],
            /* top = */ appChipBoundsInWindow[1],
            /* right = */ appChipBoundsInWindow[0] + openMenuButton.width,
            /* bottom = */ appChipBoundsInWindow[1] + openMenuButton.height
        )
    }

    private fun getHeaderStyle(header: Header): HeaderStyle {
        return HeaderStyle(
            background = getHeaderBackground(header),
@@ -529,4 +559,26 @@ internal class AppHeaderViewHolder(
        private const val LIGHT_THEME_UNFOCUSED_OPACITY = 166 // 65%
        private const val FOCUSED_OPACITY = 255
    }

    class Factory {
        fun create(
            rootView: View,
            onCaptionTouchListener: View.OnTouchListener,
            onCaptionButtonClickListener: View.OnClickListener,
            onLongClickListener: OnLongClickListener,
            onCaptionGenericMotionListener: View.OnGenericMotionListener,
            appName: CharSequence,
            appIconBitmap: Bitmap,
            onMaximizeHoverAnimationFinishedListener: () -> Unit,
        ): AppHeaderViewHolder = AppHeaderViewHolder(
            rootView,
            onCaptionTouchListener,
            onCaptionButtonClickListener,
            onLongClickListener,
            onCaptionGenericMotionListener,
            appName,
            appIconBitmap,
            onMaximizeHoverAnimationFinishedListener,
        )
    }
}
Loading