blob: 93646ab4825b1b1af01e2330dbad9c627658d5f9 [file] [log] [blame]
Selim Cinek2627d722018-01-19 12:16:49 -08001/*
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
17package com.android.systemui.statusbar.notification;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
Selim Cinek7e222c3c2018-01-25 12:22:41 -080022import android.app.ActivityManager;
Selim Cinek2627d722018-01-19 12:16:49 -080023import android.graphics.Matrix;
24import android.graphics.Rect;
25import android.os.RemoteException;
26import android.util.MathUtils;
27import android.view.IRemoteAnimationFinishedCallback;
28import android.view.IRemoteAnimationRunner;
29import android.view.RemoteAnimationAdapter;
30import android.view.RemoteAnimationTarget;
Selim Cinek2627d722018-01-19 12:16:49 -080031
32import com.android.systemui.Interpolators;
Jorim Jaggi42b04752018-05-18 18:23:08 +020033import com.android.systemui.shared.system.SurfaceControlCompat;
Jorim Jaggi64be98d2018-04-26 23:23:29 +020034import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
35import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams;
Selim Cinek2627d722018-01-19 12:16:49 -080036import com.android.systemui.statusbar.ExpandableNotificationRow;
37import com.android.systemui.statusbar.NotificationListContainer;
Selim Cinek7e222c3c2018-01-25 12:22:41 -080038import com.android.systemui.statusbar.StatusBarState;
Selim Cinek2627d722018-01-19 12:16:49 -080039import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
40import com.android.systemui.statusbar.phone.NotificationPanelView;
Selim Cinek7e222c3c2018-01-25 12:22:41 -080041import com.android.systemui.statusbar.phone.StatusBar;
Selim Cinek2627d722018-01-19 12:16:49 -080042import com.android.systemui.statusbar.phone.StatusBarWindowView;
Selim Cinek2627d722018-01-19 12:16:49 -080043
Jorim Jaggi64be98d2018-04-26 23:23:29 +020044import java.util.ArrayList;
45
Selim Cinek2627d722018-01-19 12:16:49 -080046/**
47 * A class that allows activities to be launched in a seamless way where the notification
48 * transforms nicely into the starting window.
49 */
50public 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;
Selim Cinekfc5c1992018-01-29 12:40:32 -080058 private static final long LAUNCH_TIMEOUT = 500;
Selim Cinek2627d722018-01-19 12:16:49 -080059 private final NotificationPanelView mNotificationPanel;
60 private final NotificationListContainer mNotificationContainer;
61 private final StatusBarWindowView mStatusBarWindow;
Selim Cinekfc5c1992018-01-29 12:40:32 -080062 private StatusBar mStatusBar;
63 private final Runnable mTimeoutRunnable = () -> {
64 setAnimationPending(false);
65 mStatusBar.collapsePanel(true /* animate */);
66 };
Selim Cinek7e222c3c2018-01-25 12:22:41 -080067 private boolean mAnimationPending;
Selim Cinek2627d722018-01-19 12:16:49 -080068
69 public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow,
Selim Cinek7e222c3c2018-01-25 12:22:41 -080070 StatusBar statusBar,
Selim Cinek2627d722018-01-19 12:16:49 -080071 NotificationPanelView notificationPanel,
72 NotificationListContainer container) {
73 mNotificationPanel = notificationPanel;
74 mNotificationContainer = container;
75 mStatusBarWindow = statusBarWindow;
Selim Cinek7e222c3c2018-01-25 12:22:41 -080076 mStatusBar = statusBar;
Selim Cinek2627d722018-01-19 12:16:49 -080077 }
78
Jorim Jaggi04dc5962018-01-29 18:54:13 +010079 public RemoteAnimationAdapter getLaunchAnimation(
Lucas Dupin213d7052018-05-15 16:43:38 -070080 ExpandableNotificationRow sourceNotification, boolean occluded) {
81 if (mStatusBar.getBarState() != StatusBarState.SHADE || occluded) {
Jorim Jaggi77d0f36c2018-03-16 17:49:49 +010082 return null;
83 }
Jorim Jaggi04dc5962018-01-29 18:54:13 +010084 AnimationRunner animationRunner = new AnimationRunner(sourceNotification);
85 return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION,
Jorim Jaggi64be98d2018-04-26 23:23:29 +020086 ANIMATION_DURATION - 150 /* statusBarTransitionDelay */);
Selim Cinek2627d722018-01-19 12:16:49 -080087 }
88
Selim Cinek7e222c3c2018-01-25 12:22:41 -080089 public boolean isAnimationPending() {
90 return mAnimationPending;
91 }
92
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
99 private void setAnimationPending(boolean pending) {
100 mAnimationPending = pending;
101 mStatusBarWindow.setExpandAnimationPending(pending);
Selim Cinekfc5c1992018-01-29 12:40:32 -0800102 if (pending) {
103 mStatusBarWindow.postDelayed(mTimeoutRunnable, LAUNCH_TIMEOUT);
104 } else {
105 mStatusBarWindow.removeCallbacks(mTimeoutRunnable);
106 }
Selim Cinek7e222c3c2018-01-25 12:22:41 -0800107 }
108
Selim Cinek2627d722018-01-19 12:16:49 -0800109 class AnimationRunner extends IRemoteAnimationRunner.Stub {
110
111 private final ExpandableNotificationRow mSourceNotification;
112 private final ExpandAnimationParameters mParams;
113 private final Rect mWindowCrop = new Rect();
Selim Cinek2627d722018-01-19 12:16:49 -0800114 private boolean mInstantCollapsePanel = true;
Jorim Jaggi64be98d2018-04-26 23:23:29 +0200115 private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier;
Selim Cinek2627d722018-01-19 12:16:49 -0800116
117 public AnimationRunner(ExpandableNotificationRow sourceNofitication) {
118 mSourceNotification = sourceNofitication;
119 mParams = new ExpandAnimationParameters();
Jorim Jaggi64be98d2018-04-26 23:23:29 +0200120 mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification);
Selim Cinek2627d722018-01-19 12:16:49 -0800121 }
122
123 @Override
124 public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
125 IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
126 throws RemoteException {
127 mSourceNotification.post(() -> {
Jorim Jaggi95507722018-04-24 16:33:56 +0200128 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);
Selim Cinek2627d722018-01-19 12:16:49 -0800158 }
Selim Cinek2627d722018-01-19 12:16:49 -0800159 }
160 }
Jorim Jaggi95507722018-04-24 16:33:56 +0200161 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();
Selim Cinek7e222c3c2018-01-25 12:22:41 -0800200 setAnimationPending(false);
Selim Cinek2627d722018-01-19 12:16:49 -0800201 });
202 }
203
Jorim Jaggi95507722018-04-24 16:33:56 +0200204 private void invokeCallback(IRemoteAnimationFinishedCallback callback) {
205 try {
206 callback.onAnimationFinished();
207 } catch (RemoteException e) {
208 e.printStackTrace();
209 }
210 }
211
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
Selim Cinek2627d722018-01-19 12:16:49 -0800224 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
236 private void applyParamsToNotificationList(ExpandAnimationParameters params) {
237 mNotificationContainer.applyExpandAnimationParams(params);
238 mNotificationPanel.applyExpandAnimationParams(params);
239 }
240
241 private void applyParamsToNotification(ExpandAnimationParameters params) {
242 mSourceNotification.applyExpandAnimationParams(params);
243 }
244
245 private void applyParamsToWindow(RemoteAnimationTarget app) {
Selim Cinek2627d722018-01-19 12:16:49 -0800246 Matrix m = new Matrix();
247 m.postTranslate(0, (float) (mParams.top - app.position.y));
Selim Cinek2627d722018-01-19 12:16:49 -0800248 mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight());
Jorim Jaggi42b04752018-05-18 18:23:08 +0200249 SurfaceParams params = new SurfaceParams(new SurfaceControlCompat(app.leash),
250 1f /* alpha */, m, mWindowCrop, app.prefixOrderIndex);
Jorim Jaggi64be98d2018-04-26 23:23:29 +0200251 mSyncRtTransactionApplier.scheduleApply(params);
Selim Cinek2627d722018-01-19 12:16:49 -0800252 }
253
254 @Override
255 public void onAnimationCancelled() throws RemoteException {
Selim Cinek7e222c3c2018-01-25 12:22:41 -0800256 mSourceNotification.post(() -> {
257 setAnimationPending(false);
258 mStatusBar.onLaunchAnimationCancelled();
259 });
Selim Cinek2627d722018-01-19 12:16:49 -0800260 }
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;
Selim Cinekc25989e2018-02-16 16:42:14 -0800271 int startClipTopAmount;
272 int parentStartClipTopAmount;
Selim Cinek2627d722018-01-19 12:16:49 -0800273
274 public ExpandAnimationParameters() {
275 }
276
277 public int getTop() {
278 return top;
279 }
280
Selim Cinekffd3dc62018-11-30 18:06:57 -0800281 public int getBottom() {
282 return bottom;
283 }
284
Selim Cinek2627d722018-01-19 12:16:49 -0800285 public int getWidth() {
286 return right - left;
287 }
288
289 public int getHeight() {
290 return bottom - top;
291 }
292
293 public int getTopChange() {
Selim Cinekc25989e2018-02-16 16:42:14 -0800294 // We need this compensation to ensure that the QS moves in sync.
295 int clipTopAmountCompensation = 0;
296 if (startClipTopAmount != 0.0f) {
297 clipTopAmountCompensation = (int) MathUtils.lerp(0, startClipTopAmount,
298 Interpolators.FAST_OUT_SLOW_IN.getInterpolation(linearProgress));
299 }
300 return Math.min(top - startPosition[1] - clipTopAmountCompensation, 0);
Selim Cinek2627d722018-01-19 12:16:49 -0800301 }
302
Selim Cinekc25989e2018-02-16 16:42:14 -0800303 public float getProgress() {
304 return linearProgress;
305 }
Selim Cinek2627d722018-01-19 12:16:49 -0800306
307 public float getProgress(long delay, long duration) {
308 return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay)
309 / duration, 0.0f, 1.0f);
310 }
311
Selim Cinekc25989e2018-02-16 16:42:14 -0800312 public int getStartClipTopAmount() {
313 return startClipTopAmount;
314 }
315
316 public int getParentStartClipTopAmount() {
317 return parentStartClipTopAmount;
318 }
319
Selim Cinek2627d722018-01-19 12:16:49 -0800320 public float getStartTranslationZ() {
321 return startTranslationZ;
322 }
323 }
324}