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 17 package com.android.wm.shell.common.split; 18 19 import static android.view.WindowManager.DOCKED_BOTTOM; 20 import static android.view.WindowManager.DOCKED_INVALID; 21 import static android.view.WindowManager.DOCKED_LEFT; 22 import static android.view.WindowManager.DOCKED_RIGHT; 23 import static android.view.WindowManager.DOCKED_TOP; 24 25 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; 26 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_DISMISSING; 27 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX; 28 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_NONE; 29 30 import android.graphics.Point; 31 import android.graphics.Rect; 32 import android.view.SurfaceControl; 33 34 /** 35 * This class governs how and when parallax and dimming effects are applied to task surfaces, 36 * usually when the divider is being moved around by the user (or during an animation). 37 */ 38 class ResizingEffectPolicy { 39 /** The default amount to dim an app that is partially offscreen. */ 40 public static float DEFAULT_OFFSCREEN_DIM = 0.32f; 41 42 private final SplitLayout mSplitLayout; 43 /** The parallax algorithm we are currently using. */ 44 private final int mParallaxType; 45 /** 46 * A convenience class, corresponding to {@link #mParallaxType}, that performs all the 47 * calculations for parallax and dimming values. 48 */ 49 private final ParallaxSpec mParallaxSpec; 50 51 int mShrinkSide = DOCKED_INVALID; 52 53 // The current dismissing side. 54 int mDimmingSide = DOCKED_INVALID; 55 56 /** 57 * A {@link Point} that stores a single x and y value, representing the parallax translation 58 * we use on the app that the divider is moving toward. The app is either shrinking in size or 59 * getting pushed off the screen. 60 */ 61 final Point mRetreatingSideParallax = new Point(); 62 /** 63 * A {@link Point} that stores a single x and y value, representing the parallax translation 64 * we use on the app that the divider is moving away from. The app is either growing in size or 65 * getting pulled onto the screen. 66 */ 67 final Point mAdvancingSideParallax = new Point(); 68 69 // The dimming value to hint the dismissing side and progress. 70 float mDimValue = 0.0f; 71 72 /** 73 * Content bounds for the app that the divider is moving toward. This is the content that is 74 * currently drawn at the start of the divider movement. It stays unchanged throughout the 75 * divider's movement. 76 */ 77 final Rect mRetreatingContent = new Rect(); 78 /** 79 * Surface bounds for the app that the divider is moving toward. This is the "canvas" on 80 * which an app could potentially be drawn. It changes on every frame as the divider moves 81 * around. 82 */ 83 final Rect mRetreatingSurface = new Rect(); 84 /** 85 * Content bounds for the app that the divider is moving away from. This is the content that 86 * is currently drawn at the start of the divider movement. It stays unchanged throughout 87 * the divider's movement. 88 */ 89 final Rect mAdvancingContent = new Rect(); 90 /** 91 * Surface bounds for the app that the divider is moving away from. This is the "canvas" on 92 * which an app could potentially be drawn. It changes on every frame as the divider moves 93 * around. 94 */ 95 final Rect mAdvancingSurface = new Rect(); 96 97 final Rect mTempRect = new Rect(); 98 final Rect mTempRect2 = new Rect(); 99 ResizingEffectPolicy(int parallaxType, SplitLayout splitLayout)100 ResizingEffectPolicy(int parallaxType, SplitLayout splitLayout) { 101 mParallaxType = parallaxType; 102 mSplitLayout = splitLayout; 103 switch (mParallaxType) { 104 case PARALLAX_DISMISSING: 105 mParallaxSpec = new DismissingParallaxSpec(); 106 break; 107 case PARALLAX_ALIGN_CENTER: 108 mParallaxSpec = new CenterParallaxSpec(); 109 break; 110 case PARALLAX_FLEX: 111 mParallaxSpec = new FlexParallaxSpec(); 112 break; 113 case PARALLAX_NONE: 114 default: 115 mParallaxSpec = new NoParallaxSpec(); 116 break; 117 } 118 } 119 120 /** 121 * Calculates the desired parallax and dimming values for a task surface and stores them in 122 * {@link #mRetreatingSideParallax}, {@link #mAdvancingSideParallax}, and 123 * {@link #mDimValue} These values will be then be applied in 124 * {@link #adjustRootSurface} and {@link #adjustDimSurface} respectively. 125 */ applyDividerPosition( int position, boolean isLeftRightSplit, DividerSnapAlgorithm snapAlgorithm)126 void applyDividerPosition( 127 int position, boolean isLeftRightSplit, DividerSnapAlgorithm snapAlgorithm) { 128 mDimmingSide = DOCKED_INVALID; 129 mRetreatingSideParallax.set(0, 0); 130 mAdvancingSideParallax.set(0, 0); 131 mDimValue = 0; 132 Rect displayBounds = mSplitLayout.getRootBounds(); 133 134 // Figure out which side is shrinking, and assign retreating/advancing bounds 135 final boolean topLeftShrink = isLeftRightSplit 136 ? position < mSplitLayout.getTopLeftContentBounds().right 137 : position < mSplitLayout.getTopLeftContentBounds().bottom; 138 if (topLeftShrink) { 139 mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP; 140 mRetreatingContent.set(mSplitLayout.getTopLeftContentBounds()); 141 mRetreatingSurface.set(mSplitLayout.getTopLeftBounds()); 142 mAdvancingContent.set(mSplitLayout.getBottomRightContentBounds()); 143 mAdvancingSurface.set(mSplitLayout.getBottomRightBounds()); 144 } else { 145 mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM; 146 mRetreatingContent.set(mSplitLayout.getBottomRightContentBounds()); 147 mRetreatingSurface.set(mSplitLayout.getBottomRightBounds()); 148 mAdvancingContent.set(mSplitLayout.getTopLeftContentBounds()); 149 mAdvancingSurface.set(mSplitLayout.getTopLeftBounds()); 150 } 151 152 // Figure out if we should be dimming one side 153 mDimmingSide = mParallaxSpec.getDimmingSide(position, snapAlgorithm, isLeftRightSplit); 154 155 // If so, calculate dimming 156 if (mDimmingSide != DOCKED_INVALID) { 157 mDimValue = mParallaxSpec.getDimValue(position, snapAlgorithm); 158 } 159 160 // Calculate parallax and modify mRetreatingSideParallax and mAdvancingSideParallax, for use 161 // in adjustRootSurface(). 162 mParallaxSpec.getParallax(mRetreatingSideParallax, mAdvancingSideParallax, position, 163 snapAlgorithm, isLeftRightSplit, displayBounds, mRetreatingSurface, 164 mRetreatingContent, mAdvancingSurface, mAdvancingContent, mDimmingSide, 165 topLeftShrink); 166 } 167 168 /** Applies the calculated parallax and dimming values to task surfaces. */ 169 void adjustRootSurface(SurfaceControl.Transaction t, 170 SurfaceControl leash1, SurfaceControl leash2) { 171 SurfaceControl retreatingLeash = null; 172 SurfaceControl advancingLeash = null; 173 174 if (mParallaxType == PARALLAX_DISMISSING) { 175 switch (mDimmingSide) { 176 case DOCKED_TOP: 177 case DOCKED_LEFT: 178 retreatingLeash = leash1; 179 mTempRect.set(mSplitLayout.getTopLeftBounds()); 180 advancingLeash = leash2; 181 mTempRect2.set(mSplitLayout.getBottomRightBounds()); 182 break; 183 case DOCKED_BOTTOM: 184 case DOCKED_RIGHT: 185 retreatingLeash = leash2; 186 mTempRect.set(mSplitLayout.getBottomRightBounds()); 187 advancingLeash = leash1; 188 mTempRect2.set(mSplitLayout.getTopLeftBounds()); 189 break; 190 } 191 } else if (mParallaxType == PARALLAX_ALIGN_CENTER || mParallaxType == PARALLAX_FLEX) { 192 switch (mShrinkSide) { 193 case DOCKED_TOP: 194 case DOCKED_LEFT: 195 retreatingLeash = leash1; 196 mTempRect.set(mSplitLayout.getTopLeftBounds()); 197 advancingLeash = leash2; 198 mTempRect2.set(mSplitLayout.getBottomRightBounds()); 199 break; 200 case DOCKED_BOTTOM: 201 case DOCKED_RIGHT: 202 retreatingLeash = leash2; 203 mTempRect.set(mSplitLayout.getBottomRightBounds()); 204 advancingLeash = leash1; 205 mTempRect2.set(mSplitLayout.getTopLeftBounds()); 206 break; 207 } 208 } 209 if (mParallaxType != PARALLAX_NONE 210 && retreatingLeash != null && advancingLeash != null) { 211 t.setPosition(retreatingLeash, mTempRect.left + mRetreatingSideParallax.x, 212 mTempRect.top + mRetreatingSideParallax.y); 213 // Transform the screen-based split bounds to surface-based crop bounds. 214 mTempRect.offsetTo(-mRetreatingSideParallax.x, -mRetreatingSideParallax.y); 215 t.setWindowCrop(retreatingLeash, mTempRect); 216 217 t.setPosition(advancingLeash, mTempRect2.left + mAdvancingSideParallax.x, 218 mTempRect2.top + mAdvancingSideParallax.y); 219 // Transform the screen-based split bounds to surface-based crop bounds. 220 mTempRect2.offsetTo(-mAdvancingSideParallax.x, -mAdvancingSideParallax.y); 221 t.setWindowCrop(advancingLeash, mTempRect2); 222 } 223 } 224 225 void adjustDimSurface(SurfaceControl.Transaction t, 226 SurfaceControl dimLayer1, SurfaceControl dimLayer2) { 227 SurfaceControl targetDimLayer; 228 SurfaceControl oppositeDimLayer; 229 switch (mDimmingSide) { 230 case DOCKED_TOP: 231 case DOCKED_LEFT: 232 targetDimLayer = dimLayer1; 233 oppositeDimLayer = dimLayer2; 234 break; 235 case DOCKED_BOTTOM: 236 case DOCKED_RIGHT: 237 targetDimLayer = dimLayer2; 238 oppositeDimLayer = dimLayer1; 239 break; 240 case DOCKED_INVALID: 241 default: 242 t.setAlpha(dimLayer1, 0).hide(dimLayer1); 243 t.setAlpha(dimLayer2, 0).hide(dimLayer2); 244 return; 245 } 246 t.setAlpha(targetDimLayer, mDimValue) 247 .setVisibility(targetDimLayer, mDimValue > 0.001f); 248 t.setAlpha(oppositeDimLayer, 0f) 249 .setVisibility(oppositeDimLayer, false); 250 } 251 } 252