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