1 /* 2 * Copyright (C) 2018 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.statusbar.notification; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.app.ActivityManager; 23 import android.graphics.Matrix; 24 import android.graphics.Rect; 25 import android.os.RemoteException; 26 import android.util.MathUtils; 27 import android.view.IRemoteAnimationFinishedCallback; 28 import android.view.IRemoteAnimationRunner; 29 import android.view.RemoteAnimationAdapter; 30 import android.view.RemoteAnimationTarget; 31 32 import com.android.systemui.Interpolators; 33 import com.android.systemui.shared.system.SurfaceControlCompat; 34 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; 35 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams; 36 import com.android.systemui.statusbar.ExpandableNotificationRow; 37 import com.android.systemui.statusbar.NotificationListContainer; 38 import com.android.systemui.statusbar.StatusBarState; 39 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; 40 import com.android.systemui.statusbar.phone.NotificationPanelView; 41 import com.android.systemui.statusbar.phone.StatusBar; 42 import com.android.systemui.statusbar.phone.StatusBarWindowView; 43 44 import java.util.ArrayList; 45 46 /** 47 * A class that allows activities to be launched in a seamless way where the notification 48 * transforms nicely into the starting window. 49 */ 50 public class ActivityLaunchAnimator { 51 52 private static final int ANIMATION_DURATION = 400; 53 public static final long ANIMATION_DURATION_FADE_CONTENT = 67; 54 public static final long ANIMATION_DURATION_FADE_APP = 200; 55 public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION - 56 CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY 57 - 16; 58 private static final long LAUNCH_TIMEOUT = 500; 59 private final NotificationPanelView mNotificationPanel; 60 private final NotificationListContainer mNotificationContainer; 61 private final StatusBarWindowView mStatusBarWindow; 62 private StatusBar mStatusBar; 63 private final Runnable mTimeoutRunnable = () -> { 64 setAnimationPending(false); 65 mStatusBar.collapsePanel(true /* animate */); 66 }; 67 private boolean mAnimationPending; 68 ActivityLaunchAnimator(StatusBarWindowView statusBarWindow, StatusBar statusBar, NotificationPanelView notificationPanel, NotificationListContainer container)69 public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow, 70 StatusBar statusBar, 71 NotificationPanelView notificationPanel, 72 NotificationListContainer container) { 73 mNotificationPanel = notificationPanel; 74 mNotificationContainer = container; 75 mStatusBarWindow = statusBarWindow; 76 mStatusBar = statusBar; 77 } 78 getLaunchAnimation( ExpandableNotificationRow sourceNotification, boolean occluded)79 public RemoteAnimationAdapter getLaunchAnimation( 80 ExpandableNotificationRow sourceNotification, boolean occluded) { 81 if (mStatusBar.getBarState() != StatusBarState.SHADE || occluded) { 82 return null; 83 } 84 AnimationRunner animationRunner = new AnimationRunner(sourceNotification); 85 return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION, 86 ANIMATION_DURATION - 150 /* statusBarTransitionDelay */); 87 } 88 isAnimationPending()89 public boolean isAnimationPending() { 90 return mAnimationPending; 91 } 92 setLaunchResult(int launchResult)93 public void setLaunchResult(int launchResult) { 94 setAnimationPending((launchResult == ActivityManager.START_TASK_TO_FRONT 95 || launchResult == ActivityManager.START_SUCCESS) 96 && mStatusBar.getBarState() == StatusBarState.SHADE); 97 } 98 setAnimationPending(boolean pending)99 private void setAnimationPending(boolean pending) { 100 mAnimationPending = pending; 101 mStatusBarWindow.setExpandAnimationPending(pending); 102 if (pending) { 103 mStatusBarWindow.postDelayed(mTimeoutRunnable, LAUNCH_TIMEOUT); 104 } else { 105 mStatusBarWindow.removeCallbacks(mTimeoutRunnable); 106 } 107 } 108 109 class AnimationRunner extends IRemoteAnimationRunner.Stub { 110 111 private final ExpandableNotificationRow mSourceNotification; 112 private final ExpandAnimationParameters mParams; 113 private final Rect mWindowCrop = new Rect(); 114 private boolean mInstantCollapsePanel = true; 115 private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier; 116 AnimationRunner(ExpandableNotificationRow sourceNofitication)117 public AnimationRunner(ExpandableNotificationRow sourceNofitication) { 118 mSourceNotification = sourceNofitication; 119 mParams = new ExpandAnimationParameters(); 120 mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification); 121 } 122 123 @Override onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets, IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)124 public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets, 125 IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback) 126 throws RemoteException { 127 mSourceNotification.post(() -> { 128 RemoteAnimationTarget primary = getPrimaryRemoteAnimationTarget( 129 remoteAnimationTargets); 130 if (primary == null) { 131 setAnimationPending(false); 132 invokeCallback(iRemoteAnimationFinishedCallback); 133 return; 134 } 135 136 setExpandAnimationRunning(true); 137 mInstantCollapsePanel = primary.position.y == 0 138 && primary.sourceContainerBounds.height() 139 >= mNotificationPanel.getHeight(); 140 if (!mInstantCollapsePanel) { 141 mNotificationPanel.collapseWithDuration(ANIMATION_DURATION); 142 } 143 ValueAnimator anim = ValueAnimator.ofFloat(0, 1); 144 mParams.startPosition = mSourceNotification.getLocationOnScreen(); 145 mParams.startTranslationZ = mSourceNotification.getTranslationZ(); 146 mParams.startClipTopAmount = mSourceNotification.getClipTopAmount(); 147 if (mSourceNotification.isChildInGroup()) { 148 int parentClip = mSourceNotification 149 .getNotificationParent().getClipTopAmount(); 150 mParams.parentStartClipTopAmount = parentClip; 151 // We need to calculate how much the child is clipped by the parent 152 // because children always have 0 clipTopAmount 153 if (parentClip != 0) { 154 float childClip = parentClip 155 - mSourceNotification.getTranslationY(); 156 if (childClip > 0.0f) { 157 mParams.startClipTopAmount = (int) Math.ceil(childClip); 158 } 159 } 160 } 161 int targetWidth = primary.sourceContainerBounds.width(); 162 int notificationHeight = mSourceNotification.getActualHeight() 163 - mSourceNotification.getClipBottomAmount(); 164 int notificationWidth = mSourceNotification.getWidth(); 165 anim.setDuration(ANIMATION_DURATION); 166 anim.setInterpolator(Interpolators.LINEAR); 167 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 168 @Override 169 public void onAnimationUpdate(ValueAnimator animation) { 170 mParams.linearProgress = animation.getAnimatedFraction(); 171 float progress 172 = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 173 mParams.linearProgress); 174 int newWidth = (int) MathUtils.lerp(notificationWidth, 175 targetWidth, progress); 176 mParams.left = (int) ((targetWidth - newWidth) / 2.0f); 177 mParams.right = mParams.left + newWidth; 178 mParams.top = (int) MathUtils.lerp(mParams.startPosition[1], 179 primary.position.y, progress); 180 mParams.bottom = (int) MathUtils.lerp(mParams.startPosition[1] 181 + notificationHeight, 182 primary.position.y + primary.sourceContainerBounds.bottom, 183 progress); 184 applyParamsToWindow(primary); 185 applyParamsToNotification(mParams); 186 applyParamsToNotificationList(mParams); 187 } 188 }); 189 anim.addListener(new AnimatorListenerAdapter() { 190 @Override 191 public void onAnimationEnd(Animator animation) { 192 setExpandAnimationRunning(false); 193 if (mInstantCollapsePanel) { 194 mStatusBar.collapsePanel(false /* animate */); 195 } 196 invokeCallback(iRemoteAnimationFinishedCallback); 197 } 198 }); 199 anim.start(); 200 setAnimationPending(false); 201 }); 202 } 203 invokeCallback(IRemoteAnimationFinishedCallback callback)204 private void invokeCallback(IRemoteAnimationFinishedCallback callback) { 205 try { 206 callback.onAnimationFinished(); 207 } catch (RemoteException e) { 208 e.printStackTrace(); 209 } 210 } 211 getPrimaryRemoteAnimationTarget( RemoteAnimationTarget[] remoteAnimationTargets)212 private RemoteAnimationTarget getPrimaryRemoteAnimationTarget( 213 RemoteAnimationTarget[] remoteAnimationTargets) { 214 RemoteAnimationTarget primary = null; 215 for (RemoteAnimationTarget app : remoteAnimationTargets) { 216 if (app.mode == RemoteAnimationTarget.MODE_OPENING) { 217 primary = app; 218 break; 219 } 220 } 221 return primary; 222 } 223 setExpandAnimationRunning(boolean running)224 private void setExpandAnimationRunning(boolean running) { 225 mNotificationPanel.setLaunchingNotification(running); 226 mSourceNotification.setExpandAnimationRunning(running); 227 mStatusBarWindow.setExpandAnimationRunning(running); 228 mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null); 229 if (!running) { 230 applyParamsToNotification(null); 231 applyParamsToNotificationList(null); 232 } 233 234 } 235 applyParamsToNotificationList(ExpandAnimationParameters params)236 private void applyParamsToNotificationList(ExpandAnimationParameters params) { 237 mNotificationContainer.applyExpandAnimationParams(params); 238 mNotificationPanel.applyExpandAnimationParams(params); 239 } 240 applyParamsToNotification(ExpandAnimationParameters params)241 private void applyParamsToNotification(ExpandAnimationParameters params) { 242 mSourceNotification.applyExpandAnimationParams(params); 243 } 244 applyParamsToWindow(RemoteAnimationTarget app)245 private void applyParamsToWindow(RemoteAnimationTarget app) { 246 Matrix m = new Matrix(); 247 m.postTranslate(0, (float) (mParams.top - app.position.y)); 248 mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight()); 249 SurfaceParams params = new SurfaceParams(new SurfaceControlCompat(app.leash), 250 1f /* alpha */, m, mWindowCrop, app.prefixOrderIndex); 251 mSyncRtTransactionApplier.scheduleApply(params); 252 } 253 254 @Override onAnimationCancelled()255 public void onAnimationCancelled() throws RemoteException { 256 mSourceNotification.post(() -> { 257 setAnimationPending(false); 258 mStatusBar.onLaunchAnimationCancelled(); 259 }); 260 } 261 }; 262 263 public static class ExpandAnimationParameters { 264 float linearProgress; 265 int[] startPosition; 266 float startTranslationZ; 267 int left; 268 int top; 269 int right; 270 int bottom; 271 int startClipTopAmount; 272 int parentStartClipTopAmount; 273 ExpandAnimationParameters()274 public ExpandAnimationParameters() { 275 } 276 getTop()277 public int getTop() { 278 return top; 279 } 280 getWidth()281 public int getWidth() { 282 return right - left; 283 } 284 getHeight()285 public int getHeight() { 286 return bottom - top; 287 } 288 getTopChange()289 public int getTopChange() { 290 // We need this compensation to ensure that the QS moves in sync. 291 int clipTopAmountCompensation = 0; 292 if (startClipTopAmount != 0.0f) { 293 clipTopAmountCompensation = (int) MathUtils.lerp(0, startClipTopAmount, 294 Interpolators.FAST_OUT_SLOW_IN.getInterpolation(linearProgress)); 295 } 296 return Math.min(top - startPosition[1] - clipTopAmountCompensation, 0); 297 } 298 getProgress()299 public float getProgress() { 300 return linearProgress; 301 } 302 getProgress(long delay, long duration)303 public float getProgress(long delay, long duration) { 304 return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay) 305 / duration, 0.0f, 1.0f); 306 } 307 getStartClipTopAmount()308 public int getStartClipTopAmount() { 309 return startClipTopAmount; 310 } 311 getParentStartClipTopAmount()312 public int getParentStartClipTopAmount() { 313 return parentStartClipTopAmount; 314 } 315 getStartTranslationZ()316 public float getStartTranslationZ() { 317 return startTranslationZ; 318 } 319 } 320 } 321