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