1 package com.android.dialer.widget;
2 
3 import com.google.common.annotations.VisibleForTesting;
4 
5 import android.animation.ValueAnimator;
6 import android.animation.ValueAnimator.AnimatorUpdateListener;
7 import android.app.ActionBar;
8 import android.os.Bundle;
9 import android.util.Log;
10 
11 import com.android.dialer.DialtactsActivity;
12 import com.android.phone.common.animation.AnimUtils;
13 import 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  */
20 public 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 {
isInSearchUi()45         public boolean isInSearchUi();
hasSearchQuery()46         public boolean hasSearchQuery();
shouldShowActionBar()47         public boolean shouldShowActionBar();
getActionBarHeight()48         public int getActionBarHeight();
getActionBarHideOffset()49         public int getActionBarHideOffset();
setActionBarHideOffset(int offset)50         public void setActionBarHideOffset(int offset);
51     }
52 
ActionBarController(ActivityUi activityUi, SearchEditTextLayout searchBox)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      */
isActionBarShowing()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      */
onSearchBoxTapped()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      */
onSearchUiExited()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      */
onDialpadDown()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      */
onDialpadUp()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 
slideActionBar(boolean slideUp, boolean animate)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 
setAlpha(float alphaValue)164     public void setAlpha(float alphaValue) {
165         mSearchBox.animate().alpha(alphaValue).start();
166     }
167 
setHideOffset(int offset)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      */
getHideOffset()176     public int getHideOffset() {
177         return mActivityUi.getActionBarHideOffset();
178     }
179 
getActionBarHeight()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      */
saveInstanceState(Bundle outState)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      */
restoreInstanceState(Bundle inState)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      */
restoreActionBarOffset()222     public void restoreActionBarOffset() {
223         slideActionBar(mIsActionBarSlidUp /* slideUp */, false /* animate */);
224     }
225 
226     @VisibleForTesting
getIsActionBarSlidUp()227     public boolean getIsActionBarSlidUp() {
228         return mIsActionBarSlidUp;
229     }
230 }
231