1 /* 2 * Copyright (C) 2021 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.car.carlauncher.displayarea; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 21 import static com.android.car.carlauncher.CarLauncher.TAG; 22 import static com.android.car.carlauncher.displayarea.CarDisplayAreaController.BACKGROUND_LAYER_INDEX; 23 import static com.android.car.carlauncher.displayarea.CarDisplayAreaController.FOREGROUND_LAYER_INDEX; 24 25 import android.app.ActivityOptions; 26 import android.car.Car; 27 import android.car.app.CarActivityManager; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.res.Resources; 32 import android.graphics.Rect; 33 import android.hardware.display.DisplayManager; 34 import android.os.Handler; 35 import android.util.ArrayMap; 36 import android.util.DisplayMetrics; 37 import android.util.Log; 38 import android.view.Display; 39 import android.view.SurfaceControl; 40 import android.window.DisplayAreaAppearedInfo; 41 import android.window.DisplayAreaInfo; 42 import android.window.DisplayAreaOrganizer; 43 import android.window.WindowContainerToken; 44 import android.window.WindowContainerTransaction; 45 46 import androidx.annotation.NonNull; 47 import androidx.annotation.VisibleForTesting; 48 49 import com.android.car.carlauncher.AppGridActivity; 50 import com.android.car.carlauncher.R; 51 import com.android.wm.shell.common.SyncTransactionQueue; 52 53 import java.util.List; 54 import java.util.concurrent.Executor; 55 56 import javax.annotation.Nullable; 57 58 /** 59 * Organizer for controlling the policies defined in 60 * {@link com.android.server.wm.CarDisplayAreaPolicyProvider} 61 */ 62 public class CarDisplayAreaOrganizer extends DisplayAreaOrganizer { 63 64 /** 65 * The display partition to launch applications by default. 66 */ 67 public static final int FOREGROUND_DISPLAY_AREA_ROOT = FEATURE_VENDOR_FIRST + 1; 68 69 /** 70 * Background applications task container. 71 */ 72 public static final int BACKGROUND_TASK_CONTAINER = FEATURE_VENDOR_FIRST + 2; 73 74 private static final int FEATURE_TASKDISPLAYAREA_PARENT = FEATURE_VENDOR_FIRST + 3; 75 76 /** 77 * Control bar task container. 78 */ 79 public static final int CONTROL_BAR_DISPLAY_AREA = FEATURE_VENDOR_FIRST + 4; 80 81 public static final int FEATURE_TITLE_BAR = FEATURE_VENDOR_FIRST + 5; 82 83 static final int FEATURE_VOICE_PLATE = FEATURE_VENDOR_FIRST + 6; 84 85 @Nullable 86 private final ComponentName mAssistantVoicePlateActivityName; 87 88 private static CarDisplayAreaOrganizer sCarDisplayAreaOrganizer; 89 90 private final Context mContext; 91 private final Intent mMapsIntent; 92 private final SyncTransactionQueue mTransactionQueue; 93 private final Rect mBackgroundApplicationDisplayBounds = new Rect(); 94 private final Intent mAudioControlIntent; 95 private boolean mIsShowingBackgroundDisplay; 96 private boolean mIsShowingControlBarDisplay; 97 private final CarLauncherDisplayAreaAnimationController mAnimationController; 98 private final Handler mHandlerForAnimation; 99 private final Rect mLastVisualDisplayBounds = new Rect(); 100 private final ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = 101 new ArrayMap(); 102 103 private WindowContainerToken mBackgroundDisplayToken; 104 private WindowContainerToken mForegroundDisplayToken; 105 private WindowContainerToken mControbarDisplayToken; 106 private WindowContainerToken mTitleBarDisplayToken; 107 private WindowContainerToken mVoicePlateDisplayToken; 108 private int mDpiDensity = -1; 109 private DisplayAreaAppearedInfo mBackgroundApplicationDisplay; 110 private DisplayAreaAppearedInfo mForegroundApplicationDisplay; 111 private DisplayAreaAppearedInfo mTitleBarDisplay; 112 private DisplayAreaAppearedInfo mControlBarDisplay; 113 private boolean mIsRegistered = false; 114 private boolean mIsDisplayAreaAnimating = false; 115 116 private AppGridActivity.CAR_LAUNCHER_STATE mToState; 117 DisplayAreaAnimationRunnable mDisplayAreaAnimationRunnable = null; 118 119 @VisibleForTesting 120 CarLauncherDisplayAreaAnimationCallback mDisplayAreaAnimationCallback = 121 new CarLauncherDisplayAreaAnimationCallback() { 122 @Override 123 public void onAnimationStart( 124 CarLauncherDisplayAreaAnimationController 125 .CarLauncherDisplayAreaTransitionAnimator animator) { 126 127 mIsDisplayAreaAnimating = true; 128 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 129 // Update the foreground panel layer index to animate on top of the 130 // background DA. 131 tx.setLayer(mForegroundApplicationDisplay.getLeash(), 132 BACKGROUND_LAYER_INDEX + 1); 133 tx.apply(true); 134 } 135 136 @Override 137 public void onAnimationEnd(SurfaceControl.Transaction tx, 138 CarLauncherDisplayAreaAnimationController 139 .CarLauncherDisplayAreaTransitionAnimator animator) { 140 mIsDisplayAreaAnimating = false; 141 mAnimationController.removeAnimator(animator.getToken()); 142 if (mAnimationController.isAnimatorsConsumed()) { 143 WindowContainerTransaction wct = new WindowContainerTransaction(); 144 145 if (mToState == AppGridActivity.CAR_LAUNCHER_STATE.DEFAULT) { 146 // Foreground DA opens to default height. 147 updateBackgroundDisplayBounds(wct); 148 } 149 } 150 } 151 152 @Override 153 public void onAnimationCancel( 154 CarLauncherDisplayAreaAnimationController 155 .CarLauncherDisplayAreaTransitionAnimator animator) { 156 mIsDisplayAreaAnimating = false; 157 mAnimationController.removeAnimator(animator.getToken()); 158 } 159 }; 160 161 /** 162 * Gets the instance of {@link CarDisplayAreaOrganizer}. 163 */ getInstance(Executor executor, Context context, Intent mapsIntent, Intent audioControlIntent, SyncTransactionQueue tx)164 public static CarDisplayAreaOrganizer getInstance(Executor executor, 165 Context context, Intent mapsIntent, Intent audioControlIntent, 166 SyncTransactionQueue tx) { 167 if (sCarDisplayAreaOrganizer == null) { 168 sCarDisplayAreaOrganizer = new CarDisplayAreaOrganizer(executor, 169 context, mapsIntent, audioControlIntent, tx); 170 } 171 return sCarDisplayAreaOrganizer; 172 } 173 CarDisplayAreaOrganizer(Executor executor, Context context, Intent mapsIntent, Intent audioControlIntent, SyncTransactionQueue tx)174 private CarDisplayAreaOrganizer(Executor executor, Context context, Intent mapsIntent, 175 Intent audioControlIntent, SyncTransactionQueue tx) { 176 super(executor); 177 mContext = context; 178 mMapsIntent = mapsIntent; 179 mAudioControlIntent = audioControlIntent; 180 mTransactionQueue = tx; 181 // TODO(b/201712747): Gets the Assistant Activity by resolving the indirect Intent. 182 mAssistantVoicePlateActivityName = ComponentName.unflattenFromString( 183 context.getResources().getString(R.string.config_assistantVoicePlateActivity)); 184 mAnimationController = new CarLauncherDisplayAreaAnimationController(mContext); 185 mHandlerForAnimation = mContext.getMainThreadHandler(); 186 187 Car.createCar(context, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, 188 mCarServiceLifecycleListener); 189 } 190 191 private final Car.CarServiceLifecycleListener mCarServiceLifecycleListener = 192 new Car.CarServiceLifecycleListener() { 193 @Override 194 public void onLifecycleChanged(@NonNull Car car, boolean ready) { 195 if (ready) { 196 CarActivityManager carAm = (CarActivityManager) car.getCarManager( 197 Car.CAR_ACTIVITY_SERVICE); 198 setPersistentActivity(carAm, mMapsIntent.getComponent(), 199 BACKGROUND_TASK_CONTAINER, "Background"); 200 setPersistentActivity(carAm, mAssistantVoicePlateActivityName, 201 FEATURE_VOICE_PLATE, "VoicePlate"); 202 } 203 } 204 }; 205 setPersistentActivity(CarActivityManager am, @Nullable ComponentName activity, int featureId, String featureName)206 private static void setPersistentActivity(CarActivityManager am, 207 @Nullable ComponentName activity, int featureId, String featureName) { 208 if (activity == null) { 209 Log.e(TAG, "Empty activity for " + featureName + " (" + featureId + ")"); 210 return; 211 } 212 int ret = am.setPersistentActivity(activity, DEFAULT_DISPLAY, featureId); 213 if (ret != CarActivityManager.RESULT_SUCCESS) { 214 Log.e(TAG, "Failed to set PersistentActivity: activity=" + activity 215 + ", ret=" + ret); 216 return; 217 } 218 } 219 getDpiDensity()220 int getDpiDensity() { 221 if (mDpiDensity != -1) { 222 return mDpiDensity; 223 } 224 225 DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); 226 Display display = displayManager.getDisplay(DEFAULT_DISPLAY); 227 Resources displayResources = mContext.createDisplayContext(display).getResources(); 228 mDpiDensity = displayResources.getConfiguration().densityDpi; 229 230 return mDpiDensity; 231 } 232 isDisplayAreaAnimating()233 boolean isDisplayAreaAnimating() { 234 return mIsDisplayAreaAnimating; 235 } 236 updateBackgroundDisplayBounds(WindowContainerTransaction wct)237 private void updateBackgroundDisplayBounds(WindowContainerTransaction wct) { 238 Rect backgroundApplicationDisplayBound = mBackgroundApplicationDisplayBounds; 239 WindowContainerToken backgroundDisplayToken = 240 mBackgroundApplicationDisplay.getDisplayAreaInfo().token; 241 242 int backgroundDisplayWidthDp = 243 backgroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT 244 / getDpiDensity(); 245 int backgroundDisplayHeightDp = 246 backgroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT 247 / getDpiDensity(); 248 wct.setBounds(backgroundDisplayToken, backgroundApplicationDisplayBound); 249 wct.setScreenSizeDp(backgroundDisplayToken, backgroundDisplayWidthDp, 250 backgroundDisplayHeightDp); 251 wct.setSmallestScreenWidthDp(backgroundDisplayToken, 252 Math.min(backgroundDisplayWidthDp, backgroundDisplayHeightDp)); 253 254 mTransactionQueue.runInSync(t -> { 255 t.setPosition(mBackgroundApplicationDisplay.getLeash(), 256 backgroundApplicationDisplayBound.left, 257 backgroundApplicationDisplayBound.top); 258 }); 259 260 applyTransaction(wct); 261 } 262 resetWindowsOffset()263 void resetWindowsOffset() { 264 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 265 mDisplayAreaTokenMap.forEach( 266 (token, leash) -> { 267 CarLauncherDisplayAreaAnimationController 268 .CarLauncherDisplayAreaTransitionAnimator animator = 269 mAnimationController.getAnimatorMap().remove(token); 270 if (animator != null && animator.isRunning()) { 271 animator.cancel(); 272 } 273 tx.setPosition(leash, /* x= */ 0, /* y= */ 0) 274 .setWindowCrop(leash, /* width= */ -1, /* height= */ -1) 275 .setCornerRadius(leash, /* cornerRadius= */ -1); 276 }); 277 tx.apply(); 278 } 279 280 /** 281 * Offsets the windows by a given offset on Y-axis, triggered also from screen rotation. 282 * Directly perform manipulation/offset on the leash. 283 */ scheduleOffset(int fromPos, int toPos, Rect finalBackgroundBounds, DisplayAreaAppearedInfo backgroundApplicationDisplay, DisplayAreaAppearedInfo foregroundDisplay, DisplayAreaAppearedInfo titleBarDisplay, DisplayAreaAppearedInfo controlBarDisplay, AppGridActivity.CAR_LAUNCHER_STATE toState, int durationMs)284 void scheduleOffset(int fromPos, int toPos, Rect finalBackgroundBounds, 285 DisplayAreaAppearedInfo backgroundApplicationDisplay, 286 DisplayAreaAppearedInfo foregroundDisplay, 287 DisplayAreaAppearedInfo titleBarDisplay, 288 DisplayAreaAppearedInfo controlBarDisplay, 289 AppGridActivity.CAR_LAUNCHER_STATE toState, 290 int durationMs) { 291 mToState = toState; 292 mBackgroundApplicationDisplay = backgroundApplicationDisplay; 293 mForegroundApplicationDisplay = foregroundDisplay; 294 mControlBarDisplay = controlBarDisplay; 295 mTitleBarDisplay = titleBarDisplay; 296 mDisplayAreaTokenMap.forEach( 297 (token, leash) -> { 298 if (token == mBackgroundDisplayToken) { 299 mBackgroundApplicationDisplayBounds.set(finalBackgroundBounds); 300 } else if (token == mForegroundDisplayToken) { 301 animateWindows(token, leash, fromPos, toPos, durationMs); 302 } 303 }); 304 305 if (mToState == AppGridActivity.CAR_LAUNCHER_STATE.CONTROL_BAR) { 306 WindowContainerTransaction wct = new WindowContainerTransaction(); 307 updateBackgroundDisplayBounds(wct); 308 } 309 } 310 animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos, float toPos, int durationMs)311 void animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos, 312 float toPos, int durationMs) { 313 CarLauncherDisplayAreaAnimationController.CarLauncherDisplayAreaTransitionAnimator 314 animator = 315 mAnimationController.getAnimator(token, leash, fromPos, toPos, 316 mLastVisualDisplayBounds); 317 318 319 if (animator != null) { 320 if (mDisplayAreaAnimationRunnable != null) { 321 mDisplayAreaAnimationRunnable.stopAnimation(); 322 mHandlerForAnimation.removeCallbacks(mDisplayAreaAnimationRunnable); 323 } 324 mDisplayAreaAnimationRunnable = new DisplayAreaAnimationRunnable(animator, durationMs); 325 mHandlerForAnimation.post(mDisplayAreaAnimationRunnable); 326 } 327 } 328 329 /** 330 * A custom runnable with a flag to stop running the code within the {@link #run()} method when 331 * the runnable is in the message queue. In such cases calling 332 * {@link #removeCallbacksAndMessages(null)} won't work it only stops pending messages 333 * (Runnables) not currently running runnable. 334 */ 335 private class DisplayAreaAnimationRunnable implements Runnable { 336 private boolean mStopAnimation = false; 337 private final CarLauncherDisplayAreaAnimationController 338 .CarLauncherDisplayAreaTransitionAnimator mAnimator; 339 private final int mDurationMs; 340 DisplayAreaAnimationRunnable( CarLauncherDisplayAreaAnimationController .CarLauncherDisplayAreaTransitionAnimator animator, int durationMs)341 DisplayAreaAnimationRunnable( 342 CarLauncherDisplayAreaAnimationController 343 .CarLauncherDisplayAreaTransitionAnimator animator, 344 int durationMs) { 345 mAnimator = animator; 346 mDurationMs = durationMs; 347 } 348 349 @Override run()350 public void run() { 351 if (mStopAnimation) { 352 return; 353 } 354 355 mAnimator.addDisplayAreaAnimationCallback(mDisplayAreaAnimationCallback) 356 .setDuration(mDurationMs) 357 .start(); 358 } 359 stopAnimation()360 public void stopAnimation() { 361 // we don't call animator.cancel() here because if there is only one animation call 362 // such as just to open the DA then it will get canceled here. 363 mStopAnimation = true; 364 } 365 } 366 367 @Override onDisplayAreaAppeared(@onNull DisplayAreaInfo displayAreaInfo, @NonNull SurfaceControl leash)368 public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo, 369 @NonNull SurfaceControl leash) { 370 if (displayAreaInfo.featureId == BACKGROUND_TASK_CONTAINER) { 371 mBackgroundDisplayToken = displayAreaInfo.token; 372 mIsShowingBackgroundDisplay = true; 373 } else if (displayAreaInfo.featureId == CONTROL_BAR_DISPLAY_AREA) { 374 mControbarDisplayToken = displayAreaInfo.token; 375 mIsShowingControlBarDisplay = true; 376 } else if (displayAreaInfo.featureId == FOREGROUND_DISPLAY_AREA_ROOT) { 377 mForegroundDisplayToken = displayAreaInfo.token; 378 } else if (displayAreaInfo.featureId == FEATURE_TITLE_BAR) { 379 mTitleBarDisplayToken = displayAreaInfo.token; 380 } else if (displayAreaInfo.featureId == FEATURE_VOICE_PLATE) { 381 mVoicePlateDisplayToken = displayAreaInfo.token; 382 } 383 mDisplayAreaTokenMap.put(displayAreaInfo.token, leash); 384 } 385 386 /** 387 * Launches the map in the background DA. 388 */ startMapsInBackGroundDisplayArea()389 public void startMapsInBackGroundDisplayArea() { 390 ActivityOptions options = ActivityOptions 391 .makeCustomAnimation(mContext, 392 /* enterResId= */ 0, /* exitResId= */ 0); 393 mContext.startActivity(mMapsIntent, options.toBundle()); 394 } 395 396 /** 397 * Launches the control bar in the control bar DA. 398 */ startControlBarInDisplayArea()399 public void startControlBarInDisplayArea() { 400 ActivityOptions options = ActivityOptions 401 .makeCustomAnimation(mContext, 402 /* enterResId= */ 0, /* exitResId= */ 0); 403 options.setLaunchTaskDisplayArea(mControbarDisplayToken); 404 mContext.startActivity(mAudioControlIntent, options.toBundle()); 405 } 406 407 @Override onDisplayAreaVanished(@onNull DisplayAreaInfo displayAreaInfo)408 public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) { 409 if (displayAreaInfo.featureId == BACKGROUND_TASK_CONTAINER) { 410 mIsShowingBackgroundDisplay = false; 411 } else if (displayAreaInfo.featureId == CONTROL_BAR_DISPLAY_AREA) { 412 mIsShowingControlBarDisplay = false; 413 } 414 if (!mIsRegistered) { 415 mDisplayAreaTokenMap.remove(displayAreaInfo.token); 416 } 417 } 418 419 @Override onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo)420 public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) { 421 super.onDisplayAreaInfoChanged(displayAreaInfo); 422 if (mForegroundApplicationDisplay == null) return; 423 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 424 tx.setLayer(mForegroundApplicationDisplay.getLeash(), FOREGROUND_LAYER_INDEX); 425 tx.apply(true); 426 } 427 428 @Override registerOrganizer(int displayAreaFeature)429 public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) { 430 List<DisplayAreaAppearedInfo> displayAreaInfos = 431 super.registerOrganizer(displayAreaFeature); 432 for (DisplayAreaAppearedInfo info : displayAreaInfos) { 433 onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); 434 } 435 mIsRegistered = true; 436 return displayAreaInfos; 437 } 438 439 @Override unregisterOrganizer()440 public void unregisterOrganizer() { 441 super.unregisterOrganizer(); 442 mIsRegistered = false; 443 } 444 } 445