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

Commit 106c3972 authored by Shivangi Dubey's avatar Shivangi Dubey
Browse files

Create observer flow for Caption handle state changes

Create a flow that observes changes to caption handle state.
This flow is used to trigger app handle education.
Refer to go/one-pager-caption-handle-repository for more details.

Fixes: 361038716
Fixes: 365054334
Fixes: 365926285
Test: atest WindowDecorCaptionHandleRepositoryTest
Test: atest DesktopModeWindowDecorationTests
Flag: com.android.window.flags.enable_desktop_windowing_app_handle_education
Change-Id: I7bfda0d0393de8069bb88882a56cda88ae10caab
parent 08027298
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;
@@ -143,7 +144,7 @@ import java.util.Optional;
        includes = {
                WMShellBaseModule.class,
                PipModule.class,
                ShellBackAnimationModule.class,
                ShellBackAnimationModule.class
        })
public abstract class WMShellModule {

@@ -249,6 +250,7 @@ public abstract class WMShellModule {
            AssistContentRequester assistContentRequester,
            MultiInstanceHelper multiInstanceHelper,
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
            Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
            WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
        if (DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -274,6 +276,7 @@ public abstract class WMShellModule {
                    assistContentRequester,
                    multiInstanceHelper,
                    desktopTasksLimiter,
                    windowDecorCaptionHandleRepository,
                    desktopActivityOrientationHandler,
                    windowDecorViewHostSupplier);
        }
@@ -791,6 +794,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) -> {
@@ -1366,10 +1376,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