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 androidx.window.extensions.embedding; 18 19 import static android.os.Process.THREAD_PRIORITY_DISPLAY; 20 import static android.view.RemoteAnimationTarget.MODE_CHANGING; 21 import static android.view.RemoteAnimationTarget.MODE_CLOSING; 22 import static android.view.RemoteAnimationTarget.MODE_OPENING; 23 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; 24 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; 25 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; 26 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE; 27 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; 28 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; 29 30 import android.animation.Animator; 31 import android.animation.ValueAnimator; 32 import android.graphics.Rect; 33 import android.os.Handler; 34 import android.os.HandlerThread; 35 import android.os.RemoteException; 36 import android.util.Log; 37 import android.view.IRemoteAnimationFinishedCallback; 38 import android.view.IRemoteAnimationRunner; 39 import android.view.RemoteAnimationTarget; 40 import android.view.SurfaceControl; 41 import android.view.WindowManager; 42 import android.view.animation.Animation; 43 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.function.BiFunction; 50 51 /** To run the TaskFragment animations. */ 52 class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { 53 54 private static final String TAG = "TaskFragAnimationRunner"; 55 private final Handler mHandler; 56 private final TaskFragmentAnimationSpec mAnimationSpec; 57 TaskFragmentAnimationRunner()58 TaskFragmentAnimationRunner() { 59 HandlerThread animationThread = new HandlerThread( 60 "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY); 61 animationThread.start(); 62 mHandler = animationThread.getThreadHandler(); 63 mAnimationSpec = new TaskFragmentAnimationSpec(mHandler); 64 } 65 66 @Nullable 67 private Animator mAnimator; 68 69 @Override onAnimationStart(@indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] apps, @NonNull RemoteAnimationTarget[] wallpapers, @NonNull RemoteAnimationTarget[] nonApps, @NonNull IRemoteAnimationFinishedCallback finishedCallback)70 public void onAnimationStart(@WindowManager.TransitionOldType int transit, 71 @NonNull RemoteAnimationTarget[] apps, 72 @NonNull RemoteAnimationTarget[] wallpapers, 73 @NonNull RemoteAnimationTarget[] nonApps, 74 @NonNull IRemoteAnimationFinishedCallback finishedCallback) { 75 if (wallpapers.length != 0 || nonApps.length != 0) { 76 throw new IllegalArgumentException("TaskFragment shouldn't handle animation with" 77 + "wallpaper or non-app windows."); 78 } 79 if (TaskFragmentAnimationController.DEBUG) { 80 Log.v(TAG, "onAnimationStart transit=" + transit); 81 } 82 mHandler.post(() -> startAnimation(transit, apps, finishedCallback)); 83 } 84 85 @Override onAnimationCancelled()86 public void onAnimationCancelled() { 87 mHandler.post(this::cancelAnimation); 88 } 89 90 /** Creates and starts animation. */ startAnimation(@indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] targets, @NonNull IRemoteAnimationFinishedCallback finishedCallback)91 private void startAnimation(@WindowManager.TransitionOldType int transit, 92 @NonNull RemoteAnimationTarget[] targets, 93 @NonNull IRemoteAnimationFinishedCallback finishedCallback) { 94 if (mAnimator != null) { 95 Log.w(TAG, "start new animation when the previous one is not finished yet."); 96 mAnimator.cancel(); 97 } 98 mAnimator = createAnimator(transit, targets, finishedCallback); 99 mAnimator.start(); 100 } 101 102 /** Cancels animation. */ cancelAnimation()103 private void cancelAnimation() { 104 if (mAnimator == null) { 105 return; 106 } 107 mAnimator.cancel(); 108 mAnimator = null; 109 } 110 111 /** Creates the animator given the transition type and windows. */ 112 @NonNull createAnimator(@indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] targets, @NonNull IRemoteAnimationFinishedCallback finishedCallback)113 private Animator createAnimator(@WindowManager.TransitionOldType int transit, 114 @NonNull RemoteAnimationTarget[] targets, 115 @NonNull IRemoteAnimationFinishedCallback finishedCallback) { 116 final List<TaskFragmentAnimationAdapter> adapters = 117 createAnimationAdapters(transit, targets); 118 long duration = 0; 119 for (TaskFragmentAnimationAdapter adapter : adapters) { 120 duration = Math.max(duration, adapter.getDurationHint()); 121 } 122 final ValueAnimator animator = ValueAnimator.ofFloat(0, 1); 123 animator.setDuration(duration); 124 animator.addUpdateListener((anim) -> { 125 // Update all adapters in the same transaction. 126 final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 127 for (TaskFragmentAnimationAdapter adapter : adapters) { 128 adapter.onAnimationUpdate(t, animator.getCurrentPlayTime()); 129 } 130 t.apply(); 131 }); 132 animator.addListener(new Animator.AnimatorListener() { 133 @Override 134 public void onAnimationStart(Animator animation) {} 135 136 @Override 137 public void onAnimationEnd(Animator animation) { 138 final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 139 for (TaskFragmentAnimationAdapter adapter : adapters) { 140 adapter.onAnimationEnd(t); 141 } 142 t.apply(); 143 144 try { 145 finishedCallback.onAnimationFinished(); 146 } catch (RemoteException e) { 147 e.rethrowFromSystemServer(); 148 } 149 mAnimator = null; 150 } 151 152 @Override 153 public void onAnimationCancel(Animator animation) {} 154 155 @Override 156 public void onAnimationRepeat(Animator animation) {} 157 }); 158 return animator; 159 } 160 161 /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */ 162 @NonNull createAnimationAdapters( @indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] targets)163 private List<TaskFragmentAnimationAdapter> createAnimationAdapters( 164 @WindowManager.TransitionOldType int transit, 165 @NonNull RemoteAnimationTarget[] targets) { 166 switch (transit) { 167 case TRANSIT_OLD_ACTIVITY_OPEN: 168 case TRANSIT_OLD_TASK_FRAGMENT_OPEN: 169 return createOpenAnimationAdapters(targets); 170 case TRANSIT_OLD_ACTIVITY_CLOSE: 171 case TRANSIT_OLD_TASK_FRAGMENT_CLOSE: 172 return createCloseAnimationAdapters(targets); 173 case TRANSIT_OLD_TASK_FRAGMENT_CHANGE: 174 return createChangeAnimationAdapters(targets); 175 default: 176 throw new IllegalArgumentException("Unhandled transit type=" + transit); 177 } 178 } 179 180 @NonNull createOpenAnimationAdapters( @onNull RemoteAnimationTarget[] targets)181 private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters( 182 @NonNull RemoteAnimationTarget[] targets) { 183 return createOpenCloseAnimationAdapters(targets, true /* isOpening */, 184 mAnimationSpec::loadOpenAnimation); 185 } 186 187 @NonNull createCloseAnimationAdapters( @onNull RemoteAnimationTarget[] targets)188 private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters( 189 @NonNull RemoteAnimationTarget[] targets) { 190 return createOpenCloseAnimationAdapters(targets, false /* isOpening */, 191 mAnimationSpec::loadCloseAnimation); 192 } 193 194 /** 195 * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition. 196 * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type. 197 */ 198 @NonNull createOpenCloseAnimationAdapters( @onNull RemoteAnimationTarget[] targets, boolean isOpening, @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider)199 private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters( 200 @NonNull RemoteAnimationTarget[] targets, boolean isOpening, 201 @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) { 202 // We need to know if the target window is only a partial of the whole animation screen. 203 // If so, we will need to adjust it to make the whole animation screen looks like one. 204 final List<RemoteAnimationTarget> openingTargets = new ArrayList<>(); 205 final List<RemoteAnimationTarget> closingTargets = new ArrayList<>(); 206 final Rect openingWholeScreenBounds = new Rect(); 207 final Rect closingWholeScreenBounds = new Rect(); 208 for (RemoteAnimationTarget target : targets) { 209 if (target.mode != MODE_CLOSING) { 210 openingTargets.add(target); 211 openingWholeScreenBounds.union(target.screenSpaceBounds); 212 } else { 213 closingTargets.add(target); 214 closingWholeScreenBounds.union(target.screenSpaceBounds); 215 // Union the start bounds since this may be the ClosingChanging animation. 216 closingWholeScreenBounds.union(target.startBounds); 217 } 218 } 219 220 // For OPEN transition, open windows should be above close windows. 221 // For CLOSE transition, open windows should be below close windows. 222 int offsetLayer = TYPE_LAYER_OFFSET; 223 final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>(); 224 for (RemoteAnimationTarget target : openingTargets) { 225 final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target, 226 animationProvider, openingWholeScreenBounds); 227 if (isOpening) { 228 adapter.overrideLayer(offsetLayer++); 229 } 230 adapters.add(adapter); 231 } 232 for (RemoteAnimationTarget target : closingTargets) { 233 final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target, 234 animationProvider, closingWholeScreenBounds); 235 if (!isOpening) { 236 adapter.overrideLayer(offsetLayer++); 237 } 238 adapters.add(adapter); 239 } 240 return adapters; 241 } 242 243 @NonNull createOpenCloseAnimationAdapter( @onNull RemoteAnimationTarget target, @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider, @NonNull Rect wholeAnimationBounds)244 private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter( 245 @NonNull RemoteAnimationTarget target, 246 @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider, 247 @NonNull Rect wholeAnimationBounds) { 248 final Animation animation = animationProvider.apply(target, wholeAnimationBounds); 249 return new TaskFragmentAnimationAdapter(animation, target, target.leash, 250 wholeAnimationBounds); 251 } 252 253 @NonNull createChangeAnimationAdapters( @onNull RemoteAnimationTarget[] targets)254 private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters( 255 @NonNull RemoteAnimationTarget[] targets) { 256 if (shouldUseJumpCutForChangeAnimation(targets)) { 257 return new ArrayList<>(); 258 } 259 260 final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>(); 261 for (RemoteAnimationTarget target : targets) { 262 if (target.mode == MODE_CHANGING) { 263 // This is the target with bounds change. 264 final Animation[] animations = 265 mAnimationSpec.createChangeBoundsChangeAnimations(target); 266 // Adapter for the starting snapshot leash. 267 adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter( 268 animations[0], target)); 269 // Adapter for the ending bounds changed leash. 270 adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter( 271 animations[1], target)); 272 continue; 273 } 274 275 // These are the other targets that don't have bounds change in the same transition. 276 final Animation animation; 277 if (target.hasAnimatingParent) { 278 // No-op if it will be covered by the changing parent window. 279 animation = TaskFragmentAnimationSpec.createNoopAnimation(target); 280 } else if (target.mode == MODE_CLOSING) { 281 animation = mAnimationSpec.createChangeBoundsCloseAnimation(target); 282 } else { 283 animation = mAnimationSpec.createChangeBoundsOpenAnimation(target); 284 } 285 adapters.add(new TaskFragmentAnimationAdapter(animation, target)); 286 } 287 return adapters; 288 } 289 290 /** 291 * Whether we should use jump cut for the change transition. 292 * This normally happens when opening a new secondary with the existing primary using a 293 * different split layout. This can be complicated, like from horizontal to vertical split with 294 * new split pairs. 295 * Uses a jump cut animation to simplify. 296 */ shouldUseJumpCutForChangeAnimation(@onNull RemoteAnimationTarget[] targets)297 private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) { 298 boolean hasOpeningWindow = false; 299 boolean hasClosingWindow = false; 300 for (RemoteAnimationTarget target : targets) { 301 if (target.hasAnimatingParent) { 302 continue; 303 } 304 hasOpeningWindow |= target.mode == MODE_OPENING; 305 hasClosingWindow |= target.mode == MODE_CLOSING; 306 } 307 return hasOpeningWindow && hasClosingWindow; 308 } 309 } 310