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 ActionBar getActionBar();
50    }
51
52    public ActionBarController(ActivityUi activityUi, SearchEditTextLayout searchBox) {
53        mActivityUi = activityUi;
54        mSearchBox = searchBox;
55    }
56
57    /**
58     * @return Whether or not the action bar is currently showing (both slid down and visible)
59     */
60    public boolean isActionBarShowing() {
61        return !mIsActionBarSlidUp && !mSearchBox.isFadedOut();
62    }
63
64    /**
65     * Called when the user has tapped on the collapsed search box, to start a new search query.
66     */
67    public void onSearchBoxTapped() {
68        if (DEBUG) {
69            Log.d(TAG, "OnSearchBoxTapped: isInSearchUi " + mActivityUi.isInSearchUi());
70        }
71        if (!mActivityUi.isInSearchUi()) {
72            mSearchBox.expand(true /* animate */, true /* requestFocus */);
73        }
74    }
75
76    /**
77     * Called when search UI has been exited for some reason.
78     */
79    public void onSearchUiExited() {
80        if (DEBUG) {
81            Log.d(TAG, "OnSearchUIExited: isExpanded " + mSearchBox.isExpanded()
82                    + " isFadedOut: " + mSearchBox.isFadedOut()
83                    + " shouldShowActionBar: " + mActivityUi.shouldShowActionBar());
84        }
85        if (mSearchBox.isExpanded()) {
86            mSearchBox.collapse(true /* animate */);
87        }
88        if (mSearchBox.isFadedOut()) {
89            mSearchBox.fadeIn();
90        }
91
92        if (mActivityUi.shouldShowActionBar()) {
93            slideActionBar(false /* slideUp */, false /* animate */);
94        } else {
95            slideActionBar(true /* slideUp */, false /* animate */);
96        }
97    }
98
99    /**
100     * Called to indicate that the user is trying to hide the dialpad. Should be called before
101     * any state changes have actually occurred.
102     */
103    public void onDialpadDown() {
104        if (DEBUG) {
105            Log.d(TAG, "OnDialpadDown: isInSearchUi " + mActivityUi.isInSearchUi()
106                    + " hasSearchQuery: " + mActivityUi.hasSearchQuery()
107                    + " isFadedOut: " + mSearchBox.isFadedOut()
108                    + " isExpanded: " + mSearchBox.isExpanded());
109        }
110        if (mActivityUi.isInSearchUi()) {
111            if (mActivityUi.hasSearchQuery()) {
112                if (mSearchBox.isFadedOut()) {
113                    mSearchBox.setVisible(true);
114                }
115                if (!mSearchBox.isExpanded()) {
116                    mSearchBox.expand(false /* animate */, false /* requestFocus */);
117                }
118                slideActionBar(false /* slideUp */, true /* animate */);
119            } else {
120                mSearchBox.fadeIn();
121            }
122        }
123    }
124
125    /**
126     * Called to indicate that the user is trying to show the dialpad. Should be called before
127     * any state changes have actually occurred.
128     */
129    public void onDialpadUp() {
130        if (DEBUG) {
131            Log.d(TAG, "OnDialpadUp: isInSearchUi " + mActivityUi.isInSearchUi());
132        }
133        if (mActivityUi.isInSearchUi()) {
134            slideActionBar(true /* slideUp */, true /* animate */);
135        } else {
136            // From the lists fragment
137            mSearchBox.fadeOut(mFadeOutCallback);
138        }
139    }
140
141    public void slideActionBar(boolean slideUp, boolean animate) {
142        if (DEBUG) {
143            Log.d(TAG, "Sliding actionBar - up: " + slideUp + " animate: " + animate);
144        }
145        if (animate) {
146            ValueAnimator animator =
147                    slideUp ? ValueAnimator.ofFloat(0, 1) : ValueAnimator.ofFloat(1, 0);
148            animator.addUpdateListener(new AnimatorUpdateListener() {
149                @Override
150                public void onAnimationUpdate(ValueAnimator animation) {
151                    final float value = (float) animation.getAnimatedValue();
152                    setHideOffset(
153                            (int) (mActivityUi.getActionBarHeight() * value));
154                }
155            });
156            animator.start();
157        } else {
158           setHideOffset(slideUp ? mActivityUi.getActionBarHeight() : 0);
159        }
160        mIsActionBarSlidUp = slideUp;
161    }
162
163    public void setAlpha(float alphaValue) {
164        mSearchBox.animate().alpha(alphaValue).start();
165    }
166
167    public void setHideOffset(int offset) {
168        mIsActionBarSlidUp = offset >= mActivityUi.getActionBarHeight();
169        mActivityUi.getActionBar().setHideOffset(offset);
170    }
171
172    /**
173     * @return The offset the action bar is being translated upwards by
174     */
175    public int getHideOffset() {
176        return mActivityUi.getActionBar().getHideOffset();
177    }
178
179    /**
180     * Saves the current state of the action bar into a provided {@link Bundle}
181     */
182    public void saveInstanceState(Bundle outState) {
183        outState.putBoolean(KEY_IS_SLID_UP, mIsActionBarSlidUp);
184        outState.putBoolean(KEY_IS_FADED_OUT, mSearchBox.isFadedOut());
185        outState.putBoolean(KEY_IS_EXPANDED, mSearchBox.isExpanded());
186    }
187
188    /**
189     * Restores the action bar state from a provided {@link Bundle}.
190     */
191    public void restoreInstanceState(Bundle inState) {
192        mIsActionBarSlidUp = inState.getBoolean(KEY_IS_SLID_UP);
193
194        final boolean isSearchBoxFadedOut = inState.getBoolean(KEY_IS_FADED_OUT);
195        if (isSearchBoxFadedOut) {
196            if (!mSearchBox.isFadedOut()) {
197                mSearchBox.setVisible(false);
198            }
199        } else if (mSearchBox.isFadedOut()) {
200                mSearchBox.setVisible(true);
201        }
202
203        final boolean isSearchBoxExpanded = inState.getBoolean(KEY_IS_EXPANDED);
204        if (isSearchBoxExpanded) {
205            if (!mSearchBox.isExpanded()) {
206                mSearchBox.expand(false, false);
207            }
208        } else if (mSearchBox.isExpanded()) {
209                mSearchBox.collapse(false);
210        }
211    }
212
213    /**
214     * This should be called after onCreateOptionsMenu has been called, when the actionbar has
215     * been laid out and actually has a height.
216     */
217    public void restoreActionBarOffset() {
218        slideActionBar(mIsActionBarSlidUp /* slideUp */, false /* animate */);
219    }
220
221    @VisibleForTesting
222    public boolean getIsActionBarSlidUp() {
223        return mIsActionBarSlidUp;
224    }
225}
226