1 /* 2 * Copyright (C) 2022 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.systemui.car.displayarea; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 21 import static com.android.systemui.car.displayarea.CarDisplayAreaController.BACKGROUND_LAYER_INDEX; 22 import static com.android.systemui.car.displayarea.CarDisplayAreaController.CONTROL_BAR_LAYER_INDEX; 23 24 import android.annotation.NonNull; 25 import android.car.Car; 26 import android.car.app.CarActivityManager; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.res.Resources; 31 import android.graphics.Rect; 32 import android.hardware.display.DisplayManager; 33 import android.os.Handler; 34 import android.util.ArrayMap; 35 import android.util.DisplayMetrics; 36 import android.view.Display; 37 import android.view.SurfaceControl; 38 import android.window.DisplayAreaAppearedInfo; 39 import android.window.DisplayAreaInfo; 40 import android.window.DisplayAreaOrganizer; 41 import android.window.WindowContainerToken; 42 import android.window.WindowContainerTransaction; 43 44 import com.android.systemui.R; 45 import com.android.wm.shell.common.SyncTransactionQueue; 46 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.concurrent.Executor; 50 51 import javax.inject.Inject; 52 53 /** 54 * Organizer for controlling the policies defined in 55 * {@link com.android.server.wm.CarDisplayAreaPolicyProvider} 56 */ 57 public class CarDisplayAreaOrganizer extends DisplayAreaOrganizer { 58 59 /** 60 * The display partition to launch applications by default. 61 */ 62 public static final int FOREGROUND_DISPLAY_AREA_ROOT = FEATURE_VENDOR_FIRST + 1; 63 /** 64 * Background applications task container. 65 */ 66 public static final int BACKGROUND_TASK_CONTAINER = FEATURE_VENDOR_FIRST + 2; 67 /** 68 * Control bar task container. 69 */ 70 public static final int CONTROL_BAR_DISPLAY_AREA = FEATURE_VENDOR_FIRST + 4; 71 public static final int FEATURE_TITLE_BAR = FEATURE_VENDOR_FIRST + 5; 72 static final int FEATURE_VOICE_PLATE = FEATURE_VENDOR_FIRST + 6; 73 private static final String TAG = "CarDisplayAreaOrganizer"; 74 private final ComponentName mControlBarActivityName; 75 private final List<ComponentName> mBackGroundActivities; 76 77 private final Context mContext; 78 private final SyncTransactionQueue mTransactionQueue; 79 private final Rect mForegroundApplicationDisplayBounds = new Rect(); 80 private final Rect mBackgroundApplicationDisplayBounds = new Rect(); 81 private final CarDisplayAreaAnimationController mAnimationController; 82 private final Handler mHandlerForAnimation; 83 private final ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = 84 new ArrayMap(); 85 private final Car.CarServiceLifecycleListener mCarServiceLifecycleListener = 86 new Car.CarServiceLifecycleListener() { 87 @Override 88 public void onLifecycleChanged(@NonNull Car car, boolean ready) { 89 if (ready) { 90 CarActivityManager carAm = (CarActivityManager) car.getCarManager( 91 Car.CAR_ACTIVITY_SERVICE); 92 for (ComponentName backgroundCmp : mBackGroundActivities) { 93 CarDisplayAreaUtils.setPersistentActivity(carAm, backgroundCmp, 94 BACKGROUND_TASK_CONTAINER, "Background"); 95 } 96 CarDisplayAreaUtils.setPersistentActivity(carAm, mControlBarActivityName, 97 CONTROL_BAR_DISPLAY_AREA, "ControlBar"); 98 } 99 } 100 }; 101 DisplayAreaAnimationRunnable mDisplayAreaAnimationRunnable = null; 102 private WindowContainerToken mBackgroundDisplayToken; 103 private WindowContainerToken mForegroundDisplayToken; 104 private int mDpiDensity = -1; 105 private DisplayAreaAppearedInfo mBackgroundApplicationDisplay; 106 private DisplayAreaAppearedInfo mForegroundApplicationDisplay; 107 private DisplayAreaAppearedInfo mControlBarDisplay; 108 private boolean mIsRegistered = false; 109 private boolean mIsDisplayAreaAnimating = false; 110 private DisplayAreaComponent.FOREGROUND_DA_STATE mToState; 111 private CarDisplayAreaAnimationCallback mDisplayAreaAnimationCallback = 112 new CarDisplayAreaAnimationCallback() { 113 @Override 114 public void onAnimationStart( 115 CarDisplayAreaAnimationController 116 .CarDisplayAreaTransitionAnimator animator) { 117 118 mIsDisplayAreaAnimating = true; 119 120 mTransactionQueue.runInSync(tx -> { 121 // Update the foreground panel layer index to animate on top of the 122 // background DA. 123 tx.setLayer(mBackgroundApplicationDisplay.getLeash(), 124 BACKGROUND_LAYER_INDEX); 125 tx.setLayer(mForegroundApplicationDisplay.getLeash(), 126 BACKGROUND_LAYER_INDEX + 1); 127 tx.setLayer(mControlBarDisplay.getLeash(), 128 CONTROL_BAR_LAYER_INDEX); 129 }); 130 } 131 132 @Override 133 public void onAnimationEnd(SurfaceControl.Transaction tx, 134 CarDisplayAreaAnimationController 135 .CarDisplayAreaTransitionAnimator animator) { 136 mIsDisplayAreaAnimating = false; 137 mAnimationController.removeAnimator(animator.getToken()); 138 if (mAnimationController.isAnimatorsConsumed()) { 139 WindowContainerTransaction wct = new WindowContainerTransaction(); 140 if (mToState == DisplayAreaComponent.FOREGROUND_DA_STATE.DEFAULT) { 141 // Foreground DA opens to default height. 142 updateBackgroundDisplayBounds(wct); 143 } else if (mToState 144 == DisplayAreaComponent.FOREGROUND_DA_STATE.FULL_TO_DEFAULT) { 145 updateForegroundDisplayBounds(wct); 146 updateBackgroundDisplayBounds(wct); 147 } 148 else if (mToState == DisplayAreaComponent.FOREGROUND_DA_STATE.CONTROL_BAR) { 149 Intent homeActivityIntent = new Intent(Intent.ACTION_MAIN); 150 homeActivityIntent.addCategory(Intent.CATEGORY_HOME); 151 homeActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 152 mContext.startActivity(homeActivityIntent); 153 } 154 } 155 } 156 157 @Override 158 public void onAnimationCancel( 159 CarDisplayAreaAnimationController 160 .CarDisplayAreaTransitionAnimator animator) { 161 mIsDisplayAreaAnimating = false; 162 mAnimationController.removeAnimator(animator.getToken()); 163 } 164 }; 165 166 @Inject CarDisplayAreaOrganizer(Executor executor, Context context, SyncTransactionQueue tx)167 public CarDisplayAreaOrganizer(Executor executor, Context context, SyncTransactionQueue tx) { 168 super(executor); 169 mContext = context; 170 mTransactionQueue = tx; 171 mControlBarActivityName = ComponentName.unflattenFromString( 172 context.getResources().getString(R.string.config_controlBarActivity)); 173 mBackGroundActivities = new ArrayList<>(); 174 String[] backgroundActivities = mContext.getResources().getStringArray( 175 R.array.config_backgroundActivities); 176 for (String backgroundActivity : backgroundActivities) { 177 mBackGroundActivities 178 .add(ComponentName.unflattenFromString(backgroundActivity)); 179 } 180 mAnimationController = new CarDisplayAreaAnimationController(mContext); 181 mHandlerForAnimation = mContext.getMainThreadHandler(); 182 183 Car.createCar(context, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, 184 mCarServiceLifecycleListener); 185 } 186 getDpiDensity()187 int getDpiDensity() { 188 if (mDpiDensity != -1) { 189 return mDpiDensity; 190 } 191 192 DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); 193 Display display = displayManager.getDisplay(DEFAULT_DISPLAY); 194 Resources displayResources = mContext.createDisplayContext(display).getResources(); 195 mDpiDensity = displayResources.getConfiguration().densityDpi; 196 197 return mDpiDensity; 198 } 199 isDisplayAreaAnimating()200 boolean isDisplayAreaAnimating() { 201 return mIsDisplayAreaAnimating; 202 } 203 204 // WCT will be queued in updateBackgroundDisplayBounds(). updateForegroundDisplayBounds(WindowContainerTransaction wct)205 private void updateForegroundDisplayBounds(WindowContainerTransaction wct) { 206 Rect foregroundApplicationDisplayBound = mForegroundApplicationDisplayBounds; 207 WindowContainerToken foregroundDisplayToken = 208 mForegroundApplicationDisplay.getDisplayAreaInfo().token; 209 210 int foregroundDisplayWidthDp = 211 foregroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT 212 / getDpiDensity(); 213 int foregroundDisplayHeightDp = 214 foregroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT 215 / getDpiDensity(); 216 wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBound); 217 wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp, 218 foregroundDisplayHeightDp); 219 wct.setSmallestScreenWidthDp(foregroundDisplayToken, 220 Math.min(foregroundDisplayWidthDp, foregroundDisplayHeightDp)); 221 } 222 updateBackgroundDisplayBounds(WindowContainerTransaction wct)223 private void updateBackgroundDisplayBounds(WindowContainerTransaction wct) { 224 Rect backgroundApplicationDisplayBound = mBackgroundApplicationDisplayBounds; 225 WindowContainerToken backgroundDisplayToken = 226 mBackgroundApplicationDisplay.getDisplayAreaInfo().token; 227 228 int backgroundDisplayWidthDp = 229 backgroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT 230 / getDpiDensity(); 231 int backgroundDisplayHeightDp = 232 backgroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT 233 / getDpiDensity(); 234 wct.setBounds(backgroundDisplayToken, backgroundApplicationDisplayBound); 235 wct.setScreenSizeDp(backgroundDisplayToken, backgroundDisplayWidthDp, 236 backgroundDisplayHeightDp); 237 wct.setSmallestScreenWidthDp(backgroundDisplayToken, 238 Math.min(backgroundDisplayWidthDp, backgroundDisplayHeightDp)); 239 mTransactionQueue.queue(wct); 240 241 mTransactionQueue.runInSync(t -> { 242 // Do not set window crop on backgroundApplicationDisplay. Its windowCrop should remain 243 // full screen so that IME doesn't get cropped. 244 t.setPosition(mBackgroundApplicationDisplay.getLeash(), 245 backgroundApplicationDisplayBound.left, 246 backgroundApplicationDisplayBound.top); 247 }); 248 } 249 resetWindowsOffset()250 void resetWindowsOffset() { 251 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 252 mDisplayAreaTokenMap.forEach( 253 (token, leash) -> { 254 CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator animator = 255 mAnimationController.getAnimatorMap().remove(token); 256 if (animator != null && animator.isRunning()) { 257 animator.cancel(); 258 } 259 tx.setPosition(leash, /* x= */ 0, /* y= */ 0) 260 .setWindowCrop(leash, /* width= */ -1, /* height= */ -1) 261 .setCornerRadius(leash, /* cornerRadius= */ -1); 262 }); 263 tx.apply(); 264 } 265 266 /** 267 * Offsets the windows by a given offset on Y-axis, triggered also from screen rotation. 268 * Directly perform manipulation/offset on the leash. 269 */ scheduleOffset(int fromPos, int toPos, Rect finalBackgroundBounds, Rect finalForegroundBounds, DisplayAreaAppearedInfo backgroundApplicationDisplay, DisplayAreaAppearedInfo foregroundDisplay, DisplayAreaAppearedInfo controlBarDisplay, DisplayAreaComponent.FOREGROUND_DA_STATE toState, int durationMs)270 void scheduleOffset(int fromPos, int toPos, 271 Rect finalBackgroundBounds, Rect finalForegroundBounds, 272 DisplayAreaAppearedInfo backgroundApplicationDisplay, 273 DisplayAreaAppearedInfo foregroundDisplay, 274 DisplayAreaAppearedInfo controlBarDisplay, 275 DisplayAreaComponent.FOREGROUND_DA_STATE toState, 276 int durationMs) { 277 mToState = toState; 278 mBackgroundApplicationDisplay = backgroundApplicationDisplay; 279 mForegroundApplicationDisplay = foregroundDisplay; 280 mControlBarDisplay = controlBarDisplay; 281 mDisplayAreaTokenMap.forEach( 282 (token, leash) -> { 283 if (token == mBackgroundDisplayToken) { 284 mBackgroundApplicationDisplayBounds.set(finalBackgroundBounds); 285 } else if (token == mForegroundDisplayToken) { 286 mForegroundApplicationDisplayBounds.set(finalForegroundBounds); 287 animateWindows(token, leash, fromPos, toPos, durationMs); 288 } 289 }); 290 291 if (mToState == DisplayAreaComponent.FOREGROUND_DA_STATE.CONTROL_BAR) { 292 WindowContainerTransaction wct = new WindowContainerTransaction(); 293 updateBackgroundDisplayBounds(wct); 294 } 295 } 296 animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos, float toPos, int durationMs)297 void animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos, 298 float toPos, int durationMs) { 299 CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator 300 animator = 301 mAnimationController.getAnimator(token, leash, fromPos, toPos); 302 303 304 if (animator != null) { 305 if (mDisplayAreaAnimationRunnable != null) { 306 mDisplayAreaAnimationRunnable.stopAnimation(); 307 mHandlerForAnimation.removeCallbacks(mDisplayAreaAnimationRunnable); 308 } 309 mDisplayAreaAnimationRunnable = new DisplayAreaAnimationRunnable(animator, durationMs); 310 mHandlerForAnimation.post(mDisplayAreaAnimationRunnable); 311 } 312 } 313 314 @Override onDisplayAreaAppeared(@onNull DisplayAreaInfo displayAreaInfo, @NonNull SurfaceControl leash)315 public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo, 316 @NonNull SurfaceControl leash) { 317 if (displayAreaInfo.featureId == BACKGROUND_TASK_CONTAINER) { 318 mBackgroundDisplayToken = displayAreaInfo.token; 319 } else if (displayAreaInfo.featureId == FOREGROUND_DISPLAY_AREA_ROOT) { 320 mForegroundDisplayToken = displayAreaInfo.token; 321 } 322 mDisplayAreaTokenMap.put(displayAreaInfo.token, leash); 323 } 324 325 @Override onDisplayAreaVanished(@onNull DisplayAreaInfo displayAreaInfo)326 public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) { 327 if (!mIsRegistered) { 328 mDisplayAreaTokenMap.remove(displayAreaInfo.token); 329 } 330 } 331 332 @Override registerOrganizer(int displayAreaFeature)333 public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) { 334 List<DisplayAreaAppearedInfo> displayAreaInfos = 335 super.registerOrganizer(displayAreaFeature); 336 for (DisplayAreaAppearedInfo info : displayAreaInfos) { 337 onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); 338 } 339 mIsRegistered = true; 340 return displayAreaInfos; 341 } 342 343 @Override unregisterOrganizer()344 public void unregisterOrganizer() { 345 super.unregisterOrganizer(); 346 mIsRegistered = false; 347 } 348 349 /** 350 * A custom runnable with a flag to stop running the code within the {@link #run()} method when 351 * the runnable is in the message queue. In such cases calling 352 * {@link #removeCallbacksAndMessages(null)} won't work it only stops pending messages 353 * (Runnables) not currently running runnable. 354 */ 355 private class DisplayAreaAnimationRunnable implements Runnable { 356 private final CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator mAnimator; 357 private final int mDurationMs; 358 private boolean mStopAnimation = false; 359 DisplayAreaAnimationRunnable( CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator animator, int durationMs)360 DisplayAreaAnimationRunnable( 361 CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator animator, 362 int durationMs) { 363 mAnimator = animator; 364 mDurationMs = durationMs; 365 } 366 367 @Override run()368 public void run() { 369 if (mStopAnimation) { 370 return; 371 } 372 373 mAnimator.addDisplayAreaAnimationCallback(mDisplayAreaAnimationCallback) 374 .setDuration(mDurationMs) 375 .start(); 376 } 377 stopAnimation()378 public void stopAnimation() { 379 // we don't call animator.cancel() here because if there is only one animation call 380 // such as just to open the DA then it will get canceled here. 381 mStopAnimation = true; 382 } 383 } 384 } 385