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