1/*
2 * Copyright (C) 2011 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.recent;
18
19import android.animation.Animator;
20import android.animation.AnimatorSet;
21import android.animation.AnimatorSet.Builder;
22import android.animation.ObjectAnimator;
23import android.content.res.Resources;
24import android.graphics.drawable.ColorDrawable;
25import android.graphics.drawable.Drawable;
26import android.util.Slog;
27import android.view.View;
28import android.view.ViewRootImpl;
29
30import com.android.systemui.R;
31
32/* package */ class Choreographer implements Animator.AnimatorListener {
33    // should group this into a multi-property animation
34    private static final int OPEN_DURATION = 136;
35    private static final int CLOSE_DURATION = 230;
36    private static final int SCRIM_DURATION = 400;
37    private static final String TAG = RecentsPanelView.TAG;
38    private static final boolean DEBUG = RecentsPanelView.DEBUG;
39
40    boolean mVisible;
41    int mPanelHeight;
42    RecentsPanelView mRootView;
43    View mScrimView;
44    View mContentView;
45    View mNoRecentAppsView;
46    AnimatorSet mContentAnim;
47    Animator.AnimatorListener mListener;
48
49    // the panel will start to appear this many px from the end
50    final int HYPERSPACE_OFFRAMP = 200;
51
52    public Choreographer(RecentsPanelView root, View scrim, View content,
53            View noRecentApps, Animator.AnimatorListener listener) {
54        mRootView = root;
55        mScrimView = scrim;
56        mContentView = content;
57        mListener = listener;
58        mNoRecentAppsView = noRecentApps;
59    }
60
61    void createAnimation(boolean appearing) {
62        float start, end;
63
64        // 0: on-screen
65        // height: off-screen
66        float y = mContentView.getTranslationY();
67        if (appearing) {
68            // we want to go from near-the-top to the top, unless we're half-open in the right
69            // general vicinity
70            start = (y < HYPERSPACE_OFFRAMP) ? y : HYPERSPACE_OFFRAMP;
71            end = 0;
72        } else {
73            start = y;
74            end = y;
75        }
76
77        Animator posAnim = ObjectAnimator.ofFloat(mContentView, "translationY",
78                start, end);
79        posAnim.setInterpolator(appearing
80                ? new android.view.animation.DecelerateInterpolator(2.5f)
81                : new android.view.animation.AccelerateInterpolator(2.5f));
82        posAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
83
84        Animator fadeAnim = ObjectAnimator.ofFloat(mContentView, "alpha",
85                mContentView.getAlpha(), appearing ? 1.0f : 0.0f);
86        fadeAnim.setInterpolator(appearing
87                ? new android.view.animation.AccelerateInterpolator(1.0f)
88                : new android.view.animation.AccelerateInterpolator(2.5f));
89        fadeAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
90
91        Animator noRecentAppsFadeAnim = null;
92        if (mNoRecentAppsView != null &&  // doesn't exist on large devices
93                mNoRecentAppsView.getVisibility() == View.VISIBLE) {
94            noRecentAppsFadeAnim = ObjectAnimator.ofFloat(mNoRecentAppsView, "alpha",
95                    mContentView.getAlpha(), appearing ? 1.0f : 0.0f);
96            noRecentAppsFadeAnim.setInterpolator(appearing
97                    ? new android.view.animation.AccelerateInterpolator(1.0f)
98                    : new android.view.animation.DecelerateInterpolator(1.0f));
99            noRecentAppsFadeAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
100        }
101
102        mContentAnim = new AnimatorSet();
103        final Builder builder = mContentAnim.play(fadeAnim).with(posAnim);
104
105        if (noRecentAppsFadeAnim != null) {
106            builder.with(noRecentAppsFadeAnim);
107        }
108
109        if (appearing) {
110            Drawable background = mScrimView.getBackground();
111            if (background != null) {
112                Animator bgAnim = ObjectAnimator.ofInt(background,
113                    "alpha", appearing ? 0 : 255, appearing ? 255 : 0);
114                bgAnim.setDuration(appearing ? SCRIM_DURATION : CLOSE_DURATION);
115                builder.with(bgAnim);
116            }
117        } else {
118            final Resources res = mRootView.getResources();
119            boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
120            if (!isTablet) {
121                View recentsTransitionBackground =
122                        mRootView.findViewById(R.id.recents_transition_background);
123                recentsTransitionBackground.setVisibility(View.VISIBLE);
124                Drawable bgDrawable = new ColorDrawable(0xFF000000);
125                recentsTransitionBackground.setBackground(bgDrawable);
126                Animator bgAnim = ObjectAnimator.ofInt(bgDrawable, "alpha", 0, 255);
127                bgAnim.setDuration(CLOSE_DURATION);
128                bgAnim.setInterpolator(new android.view.animation.AccelerateInterpolator(1f));
129                builder.with(bgAnim);
130            }
131        }
132        mContentAnim.addListener(this);
133        if (mListener != null) {
134            mContentAnim.addListener(mListener);
135        }
136    }
137
138    void startAnimation(boolean appearing) {
139        if (DEBUG) Slog.d(TAG, "startAnimation(appearing=" + appearing + ")");
140
141        createAnimation(appearing);
142
143        // isHardwareAccelerated() checks if we're attached to a window and if that
144        // window is HW accelerated-- we were sometimes not attached to a window
145        // and buildLayer was throwing an IllegalStateException
146        if (mContentView.isHardwareAccelerated()) {
147            mContentView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
148            mContentView.buildLayer();
149        }
150        mContentAnim.start();
151
152        mVisible = appearing;
153    }
154
155    void jumpTo(boolean appearing) {
156        mContentView.setTranslationY(appearing ? 0 : mPanelHeight);
157        if (mScrimView.getBackground() != null) {
158            mScrimView.getBackground().setAlpha(appearing ? 255 : 0);
159        }
160        View recentsTransitionBackground =
161                mRootView.findViewById(R.id.recents_transition_background);
162        recentsTransitionBackground.setVisibility(View.INVISIBLE);
163        mRootView.requestLayout();
164    }
165
166    public void setPanelHeight(int h) {
167        if (DEBUG) Slog.d(TAG, "panelHeight=" + h);
168        mPanelHeight = h;
169    }
170
171    public void onAnimationCancel(Animator animation) {
172        if (DEBUG) Slog.d(TAG, "onAnimationCancel");
173        // force this to zero so we close the window
174        mVisible = false;
175    }
176
177    public void onAnimationEnd(Animator animation) {
178        if (DEBUG) Slog.d(TAG, "onAnimationEnd");
179        if (!mVisible) {
180            mRootView.hideWindow();
181        }
182        mContentView.setLayerType(View.LAYER_TYPE_NONE, null);
183        mContentView.setAlpha(1f);
184        mContentAnim = null;
185    }
186
187    public void onAnimationRepeat(Animator animation) {
188    }
189
190    public void onAnimationStart(Animator animation) {
191    }
192}
193