1package com.android.dialer.widget;
2
3import com.google.common.annotations.VisibleForTesting;
4
5import android.animation.ValueAnimator;
6import android.animation.ValueAnimator.AnimatorUpdateListener;
7import android.app.ActionBar;
8import android.os.Bundle;
9import android.util.Log;
10
11import com.android.dialer.DialtactsActivity;
12import com.android.phone.common.animation.AnimUtils;
13import com.android.phone.common.animation.AnimUtils.AnimationCallback;
14
15/**
16 * Controls the various animated properties of the actionBar: showing/hiding, fading/revealing,
17 * and collapsing/expanding, and assigns suitable properties to the actionBar based on the
18 * current state of the UI.
19 */
20public class ActionBarController {
21    public static final boolean DEBUG = DialtactsActivity.DEBUG;
22    public static final String TAG = "ActionBarController";
23    private static final String KEY_IS_SLID_UP = "key_actionbar_is_slid_up";
24    private static final String KEY_IS_FADED_OUT = "key_actionbar_is_faded_out";
25    private static final String KEY_IS_EXPANDED = "key_actionbar_is_expanded";
26
27    private ActivityUi mActivityUi;
28    private SearchEditTextLayout mSearchBox;
29
30    private boolean mIsActionBarSlidUp;
31
32    private final AnimationCallback mFadeOutCallback = new AnimationCallback() {
33        @Override
34        public void onAnimationEnd() {
35            slideActionBar(true /* slideUp */, false /* animate */);
36        }
37
38        @Override
39        public void onAnimationCancel() {
40            slideActionBar(true /* slideUp */, false /* animate */);
41        }
42    };
43
44    public interface ActivityUi {
45        public boolean isInSearchUi();
46        public boolean hasSearchQuery();
47        public boolean shouldShowActionBar();
48        public int getActionBarHeight();
49        public int getActionBarHideOffset();
50        public void setActionBarHideOffset(int offset);
51    }
52
53    public ActionBarController(ActivityUi activityUi, SearchEditTextLayout searchBox) {
54        mActivityUi = activityUi;
55        mSearchBox = searchBox;
56    }
57
58    /**
59     * @return Whether or not the action bar is currently showing (both slid down and visible)
60     */
61    public boolean isActionBarShowing() {
62        return !mIsActionBarSlidUp && !mSearchBox.isFadedOut();
63    }
64
65    /**
66     * Called when the user has tapped on the collapsed search box, to start a new search query.
67     */
68    public void onSearchBoxTapped() {
69        if (DEBUG) {
70            Log.d(TAG, "OnSearchBoxTapped: isInSearchUi " + mActivityUi.isInSearchUi());
71        }
72        if (!mActivityUi.isInSearchUi()) {
73            mSearchBox.expand(true /* animate */, true /* requestFocus */);
74        }
75    }
76
77    /**
78     * Called when search UI has been exited for some reason.
79     */
80    public void onSearchUiExited() {
81        if (DEBUG) {
82            Log.d(TAG, "OnSearchUIExited: isExpanded " + mSearchBox.isExpanded()
83                    + " isFadedOut: " + mSearchBox.isFadedOut()
84                    + " shouldShowActionBar: " + mActivityUi.shouldShowActionBar());
85        }
86        if (mSearchBox.isExpanded()) {
87            mSearchBox.collapse(true /* animate */);
88        }
89        if (mSearchBox.isFadedOut()) {
90            mSearchBox.fadeIn();
91        }
92
93        if (mActivityUi.shouldShowActionBar()) {
94            slideActionBar(false /* slideUp */, false /* animate */);
95        } else {
96            slideActionBar(true /* slideUp */, false /* animate */);
97        }
98    }
99
100    /**
101     * Called to indicate that the user is trying to hide the dialpad. Should be called before
102     * any state changes have actually occurred.
103     */
104    public void onDialpadDown() {
105        if (DEBUG) {
106            Log.d(TAG, "OnDialpadDown: isInSearchUi " + mActivityUi.isInSearchUi()
107                    + " hasSearchQuery: " + mActivityUi.hasSearchQuery()
108                    + " isFadedOut: " + mSearchBox.isFadedOut()
109                    + " isExpanded: " + mSearchBox.isExpanded());
110        }
111        if (mActivityUi.isInSearchUi()) {
112            if (mActivityUi.hasSearchQuery()) {
113                if (mSearchBox.isFadedOut()) {
114                    mSearchBox.setVisible(true);
115                }
116                if (!mSearchBox.isExpanded()) {
117                    mSearchBox.expand(false /* animate */, false /* requestFocus */);
118                }
119                slideActionBar(false /* slideUp */, true /* animate */);
120            } else {
121                mSearchBox.fadeIn();
122            }
123        }
124    }
125
126    /**
127     * Called to indicate that the user is trying to show the dialpad. Should be called before
128     * any state changes have actually occurred.
129     */
130    public void onDialpadUp() {
131        if (DEBUG) {
132            Log.d(TAG, "OnDialpadUp: isInSearchUi " + mActivityUi.isInSearchUi());
133        }
134        if (mActivityUi.isInSearchUi()) {
135            slideActionBar(true /* slideUp */, true /* animate */);
136        } else {
137            // From the lists fragment
138            mSearchBox.fadeOut(mFadeOutCallback);
139        }
140    }
141
142    public void slideActionBar(boolean slideUp, boolean animate) {
143        if (DEBUG) {
144            Log.d(TAG, "Sliding actionBar - up: " + slideUp + " animate: " + animate);
145        }
146        if (animate) {
147            ValueAnimator animator =
148                    slideUp ? ValueAnimator.ofFloat(0, 1) : ValueAnimator.ofFloat(1, 0);
149            animator.addUpdateListener(new AnimatorUpdateListener() {
150                @Override
151                public void onAnimationUpdate(ValueAnimator animation) {
152                    final float value = (float) animation.getAnimatedValue();
153                    setHideOffset(
154                            (int) (mActivityUi.getActionBarHeight() * value));
155                }
156            });
157            animator.start();
158        } else {
159           setHideOffset(slideUp ? mActivityUi.getActionBarHeight() : 0);
160        }
161        mIsActionBarSlidUp = slideUp;
162    }
163
164    public void setAlpha(float alphaValue) {
165        mSearchBox.animate().alpha(alphaValue).start();
166    }
167
168    public void setHideOffset(int offset) {
169        mIsActionBarSlidUp = offset >= mActivityUi.getActionBarHeight();
170        mActivityUi.setActionBarHideOffset(offset);
171    }
172
173    /**
174     * @return The offset the action bar is being translated upwards by
175     */
176    public int getHideOffset() {
177        return mActivityUi.getActionBarHideOffset();
178    }
179
180    public int getActionBarHeight() {
181        return mActivityUi.getActionBarHeight();
182    }
183
184    /**
185     * Saves the current state of the action bar into a provided {@link Bundle}
186     */
187    public void saveInstanceState(Bundle outState) {
188        outState.putBoolean(KEY_IS_SLID_UP, mIsActionBarSlidUp);
189        outState.putBoolean(KEY_IS_FADED_OUT, mSearchBox.isFadedOut());
190        outState.putBoolean(KEY_IS_EXPANDED, mSearchBox.isExpanded());
191    }
192
193    /**
194     * Restores the action bar state from a provided {@link Bundle}.
195     */
196    public void restoreInstanceState(Bundle inState) {
197        mIsActionBarSlidUp = inState.getBoolean(KEY_IS_SLID_UP);
198
199        final boolean isSearchBoxFadedOut = inState.getBoolean(KEY_IS_FADED_OUT);
200        if (isSearchBoxFadedOut) {
201            if (!mSearchBox.isFadedOut()) {
202                mSearchBox.setVisible(false);
203            }
204        } else if (mSearchBox.isFadedOut()) {
205                mSearchBox.setVisible(true);
206        }
207
208        final boolean isSearchBoxExpanded = inState.getBoolean(KEY_IS_EXPANDED);
209        if (isSearchBoxExpanded) {
210            if (!mSearchBox.isExpanded()) {
211                mSearchBox.expand(false, false);
212            }
213        } else if (mSearchBox.isExpanded()) {
214                mSearchBox.collapse(false);
215        }
216    }
217
218    /**
219     * This should be called after onCreateOptionsMenu has been called, when the actionbar has
220     * been laid out and actually has a height.
221     */
222    public void restoreActionBarOffset() {
223        slideActionBar(mIsActionBarSlidUp /* slideUp */, false /* animate */);
224    }
225
226    @VisibleForTesting
227    public boolean getIsActionBarSlidUp() {
228        return mIsActionBarSlidUp;
229    }
230}
231