1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.wm.shell.windowdecor; 17 18 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; 19 20 import android.annotation.SuppressLint; 21 import android.app.ActivityManager; 22 import android.content.Context; 23 import android.graphics.Insets; 24 import android.graphics.Rect; 25 import android.graphics.Region; 26 import android.view.InsetsState; 27 import android.view.SurfaceControl; 28 import android.view.View; 29 import android.view.WindowInsets; 30 import android.window.DesktopModeFlags; 31 import android.window.WindowContainerTransaction; 32 33 import androidx.annotation.NonNull; 34 35 import com.android.wm.shell.R; 36 import com.android.wm.shell.ShellTaskOrganizer; 37 import com.android.wm.shell.common.DisplayController; 38 import com.android.wm.shell.common.ShellExecutor; 39 import com.android.wm.shell.shared.annotations.ShellBackgroundThread; 40 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; 41 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; 42 43 /** 44 * {@link WindowDecoration} to show app controls for windows on automotive. 45 */ 46 public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { 47 private WindowDecorLinearLayout mRootView; 48 private @ShellBackgroundThread final ShellExecutor mBgExecutor; 49 private final View.OnClickListener mClickListener; 50 private final RelayoutParams mRelayoutParams = new RelayoutParams(); 51 private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>(); 52 CarWindowDecoration( Context context, @android.annotation.NonNull Context userContext, DisplayController displayController, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @ShellBackgroundThread ShellExecutor bgExecutor, WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, View.OnClickListener clickListener)53 CarWindowDecoration( 54 Context context, 55 @android.annotation.NonNull Context userContext, 56 DisplayController displayController, 57 ShellTaskOrganizer taskOrganizer, 58 ActivityManager.RunningTaskInfo taskInfo, 59 SurfaceControl taskSurface, 60 @ShellBackgroundThread ShellExecutor bgExecutor, 61 WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, 62 View.OnClickListener clickListener) { 63 super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, 64 windowDecorViewHostSupplier); 65 mBgExecutor = bgExecutor; 66 mClickListener = clickListener; 67 } 68 69 @Override relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion)70 void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus, 71 @NonNull Region displayExclusionRegion) { 72 final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 73 relayout(taskInfo, t, t); 74 } 75 76 @SuppressLint("MissingPermission") relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT)77 void relayout(ActivityManager.RunningTaskInfo taskInfo, 78 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { 79 relayout(taskInfo, startT, finishT, 80 /* isCaptionVisible= */ mRelayoutParams.mIsCaptionVisible); 81 } 82 83 @SuppressLint("MissingPermission") relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean isCaptionVisible)84 void relayout(ActivityManager.RunningTaskInfo taskInfo, 85 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, 86 boolean isCaptionVisible) { 87 final WindowContainerTransaction wct = new WindowContainerTransaction(); 88 89 updateRelayoutParams(mRelayoutParams, taskInfo, isCaptionVisible); 90 91 relayout(mRelayoutParams, startT, finishT, wct, mRootView, mResult); 92 // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo 93 mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct)); 94 95 if (mResult.mRootView == null) { 96 // This means something blocks the window decor from showing, e.g. the task is hidden. 97 // Nothing is set up in this case including the decoration surface. 98 return; 99 } 100 101 if (mRootView != mResult.mRootView) { 102 mRootView = mResult.mRootView; 103 setupRootView(mResult.mRootView, mClickListener); 104 } 105 106 if (DesktopModeFlags.ENABLE_DESKTOP_APP_HANDLE_ANIMATION.isTrue()) { 107 setCaptionVisibility(mRootView, mRelayoutParams.mIsCaptionVisible); 108 } 109 } 110 setCaptionVisibility(@onNull View rootView, boolean visible)111 private void setCaptionVisibility(@NonNull View rootView, boolean visible) { 112 final int v = visible ? View.VISIBLE : View.GONE; 113 final View captionView = rootView.findViewById(getCaptionViewId()); 114 if (captionView != null) { 115 captionView.setVisibility(v); 116 } 117 } 118 119 @Override 120 @NonNull calculateValidDragArea()121 Rect calculateValidDragArea() { 122 return new Rect(); 123 } 124 125 @Override getCaptionViewId()126 int getCaptionViewId() { 127 return R.id.caption; 128 } 129 updateRelayoutParams( RelayoutParams relayoutParams, ActivityManager.RunningTaskInfo taskInfo, boolean isCaptionVisible)130 private void updateRelayoutParams( 131 RelayoutParams relayoutParams, 132 ActivityManager.RunningTaskInfo taskInfo, 133 boolean isCaptionVisible) { 134 relayoutParams.reset(); 135 relayoutParams.mRunningTaskInfo = taskInfo; 136 // todo(b/382071404): update to car specific UI 137 relayoutParams.mLayoutResId = R.layout.caption_window_decor; 138 relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; 139 relayoutParams.mIsCaptionVisible = 140 isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded; 141 relayoutParams.mCaptionTopPadding = getTopPadding(taskInfo, relayoutParams); 142 143 relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; 144 relayoutParams.mApplyStartTransactionOnDraw = true; 145 } 146 getTopPadding(ActivityManager.RunningTaskInfo taskInfo, RelayoutParams relayoutParams)147 private int getTopPadding(ActivityManager.RunningTaskInfo taskInfo, 148 RelayoutParams relayoutParams) { 149 Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); 150 InsetsState insetsState = mDisplayController.getInsetsState(taskInfo.displayId); 151 if (insetsState == null) { 152 return relayoutParams.mCaptionTopPadding; 153 } 154 Insets systemDecor = insetsState.calculateInsets(taskBounds, 155 WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(), 156 false /* ignoreVisibility */); 157 return systemDecor.top; 158 } 159 160 /** 161 * Sets up listeners when a new root view is created. 162 */ setupRootView(View rootView, View.OnClickListener onClickListener)163 private void setupRootView(View rootView, View.OnClickListener onClickListener) { 164 final View caption = rootView.findViewById(R.id.caption); 165 final View close = caption.findViewById(R.id.close_window); 166 if (close != null) { 167 close.setOnClickListener(onClickListener); 168 } 169 final View back = caption.findViewById(R.id.back_button); 170 if (back != null) { 171 back.setOnClickListener(onClickListener); 172 } 173 } 174 } 175