1 /*
2  * Copyright (C) 2015 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.launcher3;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.TimeInterpolator;
25 import android.animation.ValueAnimator;
26 import android.annotation.SuppressLint;
27 import android.content.res.Resources;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.animation.AccelerateInterpolator;
31 import android.view.animation.DecelerateInterpolator;
32 
33 import com.android.launcher3.allapps.AllAppsContainerView;
34 import com.android.launcher3.util.UiThreadCircularReveal;
35 import com.android.launcher3.util.Thunk;
36 import com.android.launcher3.widget.WidgetsContainerView;
37 
38 import java.util.HashMap;
39 
40 /**
41  * TODO: figure out what kind of tests we can write for this
42  *
43  * Things to test when changing the following class.
44  *   - Home from workspace
45  *          - from center screen
46  *          - from other screens
47  *   - Home from all apps
48  *          - from center screen
49  *          - from other screens
50  *   - Back from all apps
51  *          - from center screen
52  *          - from other screens
53  *   - Launch app from workspace and quit
54  *          - with back
55  *          - with home
56  *   - Launch app from all apps and quit
57  *          - with back
58  *          - with home
59  *   - Go to a screen that's not the default, then all
60  *     apps, and launch and app, and go back
61  *          - with back
62  *          -with home
63  *   - On workspace, long press power and go back
64  *          - with back
65  *          - with home
66  *   - On all apps, long press power and go back
67  *          - with back
68  *          - with home
69  *   - On workspace, power off
70  *   - On all apps, power off
71  *   - Launch an app and turn off the screen while in that app
72  *          - Go back with home key
73  *          - Go back with back key  TODO: make this not go to workspace
74  *          - From all apps
75  *          - From workspace
76  *   - Enter and exit car mode (becuase it causes an extra configuration changed)
77  *          - From all apps
78  *          - From the center workspace
79  *          - From another workspace
80  */
81 public class LauncherStateTransitionAnimation {
82 
83     private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
84 
85     /**
86      * Private callbacks made during transition setup.
87      */
88     private static class PrivateTransitionCallbacks {
89         private final float materialRevealViewFinalAlpha;
90 
PrivateTransitionCallbacks(float revealAlpha)91         PrivateTransitionCallbacks(float revealAlpha) {
92             materialRevealViewFinalAlpha = revealAlpha;
93         }
94 
getMaterialRevealViewStartFinalRadius()95         float getMaterialRevealViewStartFinalRadius() {
96             return 0;
97         }
getMaterialRevealViewAnimatorListener(View revealView, View buttonView)98         AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
99                 View buttonView) {
100             return null;
101         }
onTransitionComplete()102         void onTransitionComplete() {}
103     }
104 
105     public static final String TAG = "LSTAnimation";
106 
107     // Flags to determine how to set the layers on views before the transition animation
108     public static final int BUILD_LAYER = 0;
109     public static final int BUILD_AND_SET_LAYER = 1;
110     public static final int SINGLE_FRAME_DELAY = 16;
111 
112     @Thunk Launcher mLauncher;
113     @Thunk AnimatorSet mCurrentAnimation;
114 
LauncherStateTransitionAnimation(Launcher l)115     public LauncherStateTransitionAnimation(Launcher l) {
116         mLauncher = l;
117     }
118 
119     /**
120      * Starts an animation to the apps view.
121      *
122      * @param startSearchAfterTransition Immediately starts app search after the transition to
123      *                                   All Apps is completed.
124      */
startAnimationToAllApps(final Workspace.State fromWorkspaceState, final boolean animated, final boolean startSearchAfterTransition)125     public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
126             final boolean animated, final boolean startSearchAfterTransition) {
127         final AllAppsContainerView toView = mLauncher.getAppsView();
128         final View buttonView = mLauncher.getAllAppsButton();
129         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
130             @Override
131             public float getMaterialRevealViewStartFinalRadius() {
132                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
133                 return allAppsButtonSize / 2;
134             }
135             @Override
136             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
137                     final View revealView, final View allAppsButtonView) {
138                 return new AnimatorListenerAdapter() {
139                     public void onAnimationStart(Animator animation) {
140                         allAppsButtonView.setVisibility(View.INVISIBLE);
141                     }
142                     public void onAnimationEnd(Animator animation) {
143                         allAppsButtonView.setVisibility(View.VISIBLE);
144                     }
145                 };
146             }
147             @Override
148             void onTransitionComplete() {
149                 if (startSearchAfterTransition) {
150                     toView.startAppsSearch();
151                 }
152             }
153         };
154         // Only animate the search bar if animating from spring loaded mode back to all apps
155         mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
156                 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, cb);
157     }
158 
159     /**
160      * Starts an animation to the widgets view.
161      */
startAnimationToWidgets(final Workspace.State fromWorkspaceState, final boolean animated)162     public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
163             final boolean animated) {
164         final WidgetsContainerView toView = mLauncher.getWidgetsView();
165         final View buttonView = mLauncher.getWidgetsButton();
166 
167         mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
168                 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated,
169                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS));
170     }
171 
172     /**
173      * Starts and animation to the workspace from the current overlay view.
174      */
startAnimationToWorkspace(final Launcher.State fromState, final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable)175     public void startAnimationToWorkspace(final Launcher.State fromState,
176             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
177             final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
178         if (toWorkspaceState != Workspace.State.NORMAL &&
179                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
180                 toWorkspaceState != Workspace.State.OVERVIEW) {
181             Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
182         }
183 
184         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
185             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
186                     animated, onCompleteRunnable);
187         } else {
188             startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
189                     animated, onCompleteRunnable);
190         }
191     }
192 
193     /**
194      * Creates and starts a new animation to a particular overlay view.
195      */
196     @SuppressLint("NewApi")
startAnimationToOverlay( final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final View buttonView, final BaseContainerView toView, final boolean animated, final PrivateTransitionCallbacks pCb)197     private AnimatorSet startAnimationToOverlay(
198             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
199             final View buttonView, final BaseContainerView toView,
200             final boolean animated, final PrivateTransitionCallbacks pCb) {
201         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
202         final Resources res = mLauncher.getResources();
203         final boolean material = Utilities.ATLEAST_LOLLIPOP;
204         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
205         final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
206 
207         final View fromView = mLauncher.getWorkspace();
208 
209         final HashMap<View, Integer> layerViews = new HashMap<>();
210 
211         // If for some reason our views aren't initialized, don't animate
212         boolean initialized = buttonView != null;
213 
214         // Cancel the current animation
215         cancelAnimation();
216 
217         // Create the workspace animation.
218         // NOTE: this call apparently also sets the state for the workspace if !animated
219         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
220                 animated, layerViews);
221 
222         // Animate the search bar
223         startWorkspaceSearchBarAnimation(
224                 toWorkspaceState, animated ? revealDuration : 0, animation);
225 
226         Animator updateTransitionStepAnim = dispatchOnLauncherTransitionStepAnim(fromView, toView);
227 
228         final View contentView = toView.getContentView();
229 
230         if (animated && initialized) {
231             // Setup the reveal view animation
232             final View revealView = toView.getRevealView();
233 
234             int width = revealView.getMeasuredWidth();
235             int height = revealView.getMeasuredHeight();
236             float revealRadius = (float) Math.hypot(width / 2, height / 2);
237             revealView.setVisibility(View.VISIBLE);
238             revealView.setAlpha(0f);
239             revealView.setTranslationY(0f);
240             revealView.setTranslationX(0f);
241 
242             // Calculate the final animation values
243             final float revealViewToAlpha;
244             final float revealViewToXDrift;
245             final float revealViewToYDrift;
246             if (material) {
247                 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
248                         revealView, buttonView, null);
249                 revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
250                 revealViewToYDrift = buttonViewToPanelDelta[1];
251                 revealViewToXDrift = buttonViewToPanelDelta[0];
252             } else {
253                 revealViewToAlpha = 0f;
254                 revealViewToYDrift = 2 * height / 3;
255                 revealViewToXDrift = 0;
256             }
257 
258             // Create the animators
259             PropertyValuesHolder panelAlpha =
260                     PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f);
261             PropertyValuesHolder panelDriftY =
262                     PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0);
263             PropertyValuesHolder panelDriftX =
264                     PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0);
265             ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
266                     panelAlpha, panelDriftY, panelDriftX);
267             panelAlphaAndDrift.setDuration(revealDuration);
268             panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
269 
270             // Play the animation
271             layerViews.put(revealView, BUILD_AND_SET_LAYER);
272             animation.play(panelAlphaAndDrift);
273 
274             // Setup the animation for the content view
275             contentView.setVisibility(View.VISIBLE);
276             contentView.setAlpha(0f);
277             contentView.setTranslationY(revealViewToYDrift);
278             layerViews.put(contentView, BUILD_AND_SET_LAYER);
279 
280             // Create the individual animators
281             ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
282                     revealViewToYDrift, 0);
283             pageDrift.setDuration(revealDuration);
284             pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
285             pageDrift.setStartDelay(itemsAlphaStagger);
286             animation.play(pageDrift);
287 
288             ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
289             itemsAlpha.setDuration(revealDuration);
290             itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
291             itemsAlpha.setStartDelay(itemsAlphaStagger);
292             animation.play(itemsAlpha);
293 
294             if (material) {
295                 float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
296                 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
297                         revealView, buttonView);
298                 Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2,
299                         height / 2, startRadius, revealRadius);
300                 reveal.setDuration(revealDuration);
301                 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
302                 if (listener != null) {
303                     reveal.addListener(listener);
304                 }
305                 animation.play(reveal);
306             }
307 
308             animation.addListener(new AnimatorListenerAdapter() {
309                 @Override
310                 public void onAnimationEnd(Animator animation) {
311                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
312                     dispatchOnLauncherTransitionEnd(toView, animated, false);
313 
314                     // Hide the reveal view
315                     revealView.setVisibility(View.INVISIBLE);
316 
317                     // Disable all necessary layers
318                     for (View v : layerViews.keySet()) {
319                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
320                             v.setLayerType(View.LAYER_TYPE_NONE, null);
321                         }
322                     }
323 
324                     // This can hold unnecessary references to views.
325                     cleanupAnimation();
326                     pCb.onTransitionComplete();
327                 }
328 
329             });
330 
331             // Play the workspace animation
332             if (workspaceAnim != null) {
333                 animation.play(workspaceAnim);
334             }
335 
336             animation.play(updateTransitionStepAnim);
337 
338             // Dispatch the prepare transition signal
339             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
340             dispatchOnLauncherTransitionPrepare(toView, animated, false);
341 
342             final AnimatorSet stateAnimation = animation;
343             final Runnable startAnimRunnable = new Runnable() {
344                 public void run() {
345                     // Check that mCurrentAnimation hasn't changed while
346                     // we waited for a layout/draw pass
347                     if (mCurrentAnimation != stateAnimation)
348                         return;
349                     dispatchOnLauncherTransitionStart(fromView, animated, false);
350                     dispatchOnLauncherTransitionStart(toView, animated, false);
351 
352                     // Enable all necessary layers
353                     for (View v : layerViews.keySet()) {
354                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
355                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
356                         }
357                         if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
358                             v.buildLayer();
359                         }
360                     }
361 
362                     // Focus the new view
363                     toView.requestFocus();
364 
365                     stateAnimation.start();
366                 }
367             };
368             toView.bringToFront();
369             toView.setVisibility(View.VISIBLE);
370             toView.post(startAnimRunnable);
371 
372             return animation;
373         } else {
374             toView.setTranslationX(0.0f);
375             toView.setTranslationY(0.0f);
376             toView.setScaleX(1.0f);
377             toView.setScaleY(1.0f);
378             toView.setVisibility(View.VISIBLE);
379             toView.bringToFront();
380 
381             // Show the content view
382             contentView.setVisibility(View.VISIBLE);
383 
384             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
385             dispatchOnLauncherTransitionStart(fromView, animated, false);
386             dispatchOnLauncherTransitionEnd(fromView, animated, false);
387             dispatchOnLauncherTransitionPrepare(toView, animated, false);
388             dispatchOnLauncherTransitionStart(toView, animated, false);
389             dispatchOnLauncherTransitionEnd(toView, animated, false);
390             pCb.onTransitionComplete();
391 
392             return null;
393         }
394     }
395 
396     /**
397      * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
398      * {@param fromView} and {@param toView} as the animation interpolates.
399      *
400      * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
401      */
dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView)402     private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
403         ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
404         updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
405             @Override
406             public void onAnimationUpdate(ValueAnimator animation) {
407                 dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
408                 dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
409             }
410         });
411         return updateAnimator;
412     }
413 
414     /**
415      * Starts and animation to the workspace from the apps view.
416      */
startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable)417     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
418             final Workspace.State toWorkspaceState, final int toWorkspacePage,
419             final boolean animated, final Runnable onCompleteRunnable) {
420         AllAppsContainerView appsView = mLauncher.getAppsView();
421         // No alpha anim from all apps
422         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
423             @Override
424             float getMaterialRevealViewStartFinalRadius() {
425                 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
426                 return allAppsButtonSize / 2;
427             }
428             @Override
429             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
430                     final View revealView, final View allAppsButtonView) {
431                 return new AnimatorListenerAdapter() {
432                     public void onAnimationStart(Animator animation) {
433                         // We set the alpha instead of visibility to ensure that the focus does not
434                         // get taken from the all apps view
435                         allAppsButtonView.setVisibility(View.VISIBLE);
436                         allAppsButtonView.setAlpha(0f);
437                     }
438                     public void onAnimationEnd(Animator animation) {
439                         // Hide the reveal view
440                         revealView.setVisibility(View.INVISIBLE);
441 
442                         // Show the all apps button, and focus it
443                         allAppsButtonView.setAlpha(1f);
444                     }
445                 };
446             }
447         };
448         // Only animate the search bar if animating to spring loaded mode from all apps
449         mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
450                 toWorkspacePage, mLauncher.getAllAppsButton(), appsView,
451                 animated, onCompleteRunnable, cb);
452     }
453 
454     /**
455      * Starts and animation to the workspace from the widgets view.
456      */
startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable)457     private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
458             final Workspace.State toWorkspaceState, final int toWorkspacePage,
459             final boolean animated, final Runnable onCompleteRunnable) {
460         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
461         PrivateTransitionCallbacks cb =
462                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
463             @Override
464             public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
465                     final View revealView, final View widgetsButtonView) {
466                 return new AnimatorListenerAdapter() {
467                     public void onAnimationEnd(Animator animation) {
468                         // Hide the reveal view
469                         revealView.setVisibility(View.INVISIBLE);
470                     }
471                 };
472             }
473         };
474         mCurrentAnimation = startAnimationToWorkspaceFromOverlay(
475                 fromWorkspaceState, toWorkspaceState,
476                 toWorkspacePage, mLauncher.getWidgetsButton(), widgetsView,
477                 animated, onCompleteRunnable, cb);
478     }
479 
480     /**
481      * Creates and starts a new animation to the workspace.
482      */
startAnimationToWorkspaceFromOverlay( final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final View buttonView, final BaseContainerView fromView, final boolean animated, final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb)483     private AnimatorSet startAnimationToWorkspaceFromOverlay(
484             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
485             final int toWorkspacePage,
486             final View buttonView, final BaseContainerView fromView,
487             final boolean animated, final Runnable onCompleteRunnable,
488             final PrivateTransitionCallbacks pCb) {
489         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
490         final Resources res = mLauncher.getResources();
491         final boolean material = Utilities.ATLEAST_LOLLIPOP;
492         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
493         final int itemsAlphaStagger =
494                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
495 
496         final View toView = mLauncher.getWorkspace();
497 
498         final HashMap<View, Integer> layerViews = new HashMap<>();
499 
500         // If for some reason our views aren't initialized, don't animate
501         boolean initialized = buttonView != null;
502 
503         // Cancel the current animation
504         cancelAnimation();
505 
506         // Create the workspace animation.
507         // NOTE: this call apparently also sets the state for the workspace if !animated
508         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
509                 toWorkspacePage, animated, layerViews);
510 
511         // Animate the search bar
512         startWorkspaceSearchBarAnimation(
513                 toWorkspaceState, animated ? revealDuration : 0, animation);
514 
515         Animator updateTransitionStepAnim = dispatchOnLauncherTransitionStepAnim(fromView, toView);
516 
517         if (animated && initialized) {
518             // Play the workspace animation
519             if (workspaceAnim != null) {
520                 animation.play(workspaceAnim);
521             }
522 
523             animation.play(updateTransitionStepAnim);
524             final View revealView = fromView.getRevealView();
525             final View contentView = fromView.getContentView();
526 
527             // hideAppsCustomizeHelper is called in some cases when it is already hidden
528             // don't perform all these no-op animations. In particularly, this was causing
529             // the all-apps button to pop in and out.
530             if (fromView.getVisibility() == View.VISIBLE) {
531                 int width = revealView.getMeasuredWidth();
532                 int height = revealView.getMeasuredHeight();
533                 float revealRadius = (float) Math.hypot(width / 2, height / 2);
534                 revealView.setVisibility(View.VISIBLE);
535                 revealView.setAlpha(1f);
536                 revealView.setTranslationY(0);
537                 layerViews.put(revealView, BUILD_AND_SET_LAYER);
538 
539                 // Calculate the final animation values
540                 final float revealViewToXDrift;
541                 final float revealViewToYDrift;
542                 if (material) {
543                     int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
544                             buttonView, null);
545                     revealViewToYDrift = buttonViewToPanelDelta[1];
546                     revealViewToXDrift = buttonViewToPanelDelta[0];
547                 } else {
548                     revealViewToYDrift = 2 * height / 3;
549                     revealViewToXDrift = 0;
550                 }
551 
552                 // The vertical motion of the apps panel should be delayed by one frame
553                 // from the conceal animation in order to give the right feel. We correspondingly
554                 // shorten the duration so that the slide and conceal end at the same time.
555                 TimeInterpolator decelerateInterpolator = material ?
556                         new LogDecelerateInterpolator(100, 0) :
557                         new DecelerateInterpolator(1f);
558                 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
559                         0, revealViewToYDrift);
560                 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
561                 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
562                 panelDriftY.setInterpolator(decelerateInterpolator);
563                 animation.play(panelDriftY);
564 
565                 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
566                         0, revealViewToXDrift);
567                 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
568                 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
569                 panelDriftX.setInterpolator(decelerateInterpolator);
570                 animation.play(panelDriftX);
571 
572                 // Setup animation for the reveal panel alpha
573                 final float revealViewToAlpha = !material ? 0f :
574                         pCb.materialRevealViewFinalAlpha;
575                 if (revealViewToAlpha != 1f) {
576                     ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
577                             1f, revealViewToAlpha);
578                     panelAlpha.setDuration(material ? revealDuration : 150);
579                     panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
580                     panelAlpha.setInterpolator(decelerateInterpolator);
581                     animation.play(panelAlpha);
582                 }
583 
584                 // Setup the animation for the content view
585                 layerViews.put(contentView, BUILD_AND_SET_LAYER);
586 
587                 // Create the individual animators
588                 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
589                         0, revealViewToYDrift);
590                 contentView.setTranslationY(0);
591                 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
592                 pageDrift.setInterpolator(decelerateInterpolator);
593                 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
594                 animation.play(pageDrift);
595 
596                 contentView.setAlpha(1f);
597                 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
598                 itemsAlpha.setDuration(100);
599                 itemsAlpha.setInterpolator(decelerateInterpolator);
600                 animation.play(itemsAlpha);
601 
602                 if (material) {
603                     // Animate the all apps button
604                     float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
605                     AnimatorListenerAdapter listener =
606                             pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
607                     Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2,
608                             height / 2, revealRadius, finalRadius);
609                     reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
610                     reveal.setDuration(revealDuration);
611                     reveal.setStartDelay(itemsAlphaStagger);
612                     if (listener != null) {
613                         reveal.addListener(listener);
614                     }
615                     animation.play(reveal);
616                 }
617             }
618 
619             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
620             dispatchOnLauncherTransitionPrepare(toView, animated, true);
621 
622             animation.addListener(new AnimatorListenerAdapter() {
623                 @Override
624                 public void onAnimationEnd(Animator animation) {
625                     fromView.setVisibility(View.GONE);
626                     dispatchOnLauncherTransitionEnd(fromView, animated, true);
627                     dispatchOnLauncherTransitionEnd(toView, animated, true);
628 
629                     // Run any queued runnables
630                     if (onCompleteRunnable != null) {
631                         onCompleteRunnable.run();
632                     }
633 
634                     // Disable all necessary layers
635                     for (View v : layerViews.keySet()) {
636                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
637                             v.setLayerType(View.LAYER_TYPE_NONE, null);
638                         }
639                     }
640 
641                     // Reset page transforms
642                     if (contentView != null) {
643                         contentView.setTranslationX(0);
644                         contentView.setTranslationY(0);
645                         contentView.setAlpha(1);
646                     }
647 
648                     // This can hold unnecessary references to views.
649                     cleanupAnimation();
650                     pCb.onTransitionComplete();
651                 }
652             });
653 
654             final AnimatorSet stateAnimation = animation;
655             final Runnable startAnimRunnable = new Runnable() {
656                 public void run() {
657                     // Check that mCurrentAnimation hasn't changed while
658                     // we waited for a layout/draw pass
659                     if (mCurrentAnimation != stateAnimation)
660                         return;
661 
662                     dispatchOnLauncherTransitionStart(fromView, animated, false);
663                     dispatchOnLauncherTransitionStart(toView, animated, false);
664 
665                     // Enable all necessary layers
666                     for (View v : layerViews.keySet()) {
667                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
668                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
669                         }
670                         if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
671                             v.buildLayer();
672                         }
673                     }
674                     stateAnimation.start();
675                 }
676             };
677             fromView.post(startAnimRunnable);
678 
679             return animation;
680         } else {
681             fromView.setVisibility(View.GONE);
682             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
683             dispatchOnLauncherTransitionStart(fromView, animated, true);
684             dispatchOnLauncherTransitionEnd(fromView, animated, true);
685             dispatchOnLauncherTransitionPrepare(toView, animated, true);
686             dispatchOnLauncherTransitionStart(toView, animated, true);
687             dispatchOnLauncherTransitionEnd(toView, animated, true);
688             pCb.onTransitionComplete();
689 
690             // Run any queued runnables
691             if (onCompleteRunnable != null) {
692                 onCompleteRunnable.run();
693             }
694 
695             return null;
696         }
697     }
698 
699     /**
700      * Coordinates the workspace search bar animation along with the launcher state animation.
701      */
startWorkspaceSearchBarAnimation( final Workspace.State toWorkspaceState, int duration, AnimatorSet animation)702     private void startWorkspaceSearchBarAnimation(
703             final Workspace.State toWorkspaceState, int duration, AnimatorSet animation) {
704         final SearchDropTargetBar.State toSearchBarState =
705                 toWorkspaceState.searchDropTargetBarState;
706         mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration, animation);
707     }
708 
709     /**
710      * Dispatches the prepare-transition event to suitable views.
711      */
dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace)712     void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
713         if (v instanceof LauncherTransitionable) {
714             ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
715                     toWorkspace);
716         }
717     }
718 
719     /**
720      * Dispatches the start-transition event to suitable views.
721      */
dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace)722     void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
723         if (v instanceof LauncherTransitionable) {
724             ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
725                     toWorkspace);
726         }
727 
728         // Update the workspace transition step as well
729         dispatchOnLauncherTransitionStep(v, 0f);
730     }
731 
732     /**
733      * Dispatches the step-transition event to suitable views.
734      */
dispatchOnLauncherTransitionStep(View v, float t)735     void dispatchOnLauncherTransitionStep(View v, float t) {
736         if (v instanceof LauncherTransitionable) {
737             ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
738         }
739     }
740 
741     /**
742      * Dispatches the end-transition event to suitable views.
743      */
dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace)744     void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
745         if (v instanceof LauncherTransitionable) {
746             ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
747                     toWorkspace);
748         }
749 
750         // Update the workspace transition step as well
751         dispatchOnLauncherTransitionStep(v, 1f);
752     }
753 
754     /**
755      * Cancels the current animation.
756      */
cancelAnimation()757     private void cancelAnimation() {
758         if (mCurrentAnimation != null) {
759             mCurrentAnimation.setDuration(0);
760             mCurrentAnimation.cancel();
761             mCurrentAnimation = null;
762         }
763     }
764 
cleanupAnimation()765     @Thunk void cleanupAnimation() {
766         mCurrentAnimation = null;
767     }
768 }
769