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