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 */
16package com.android.dialer.widget;
17
18import com.google.common.annotations.VisibleForTesting;
19
20import android.animation.ValueAnimator;
21import android.animation.ValueAnimator.AnimatorUpdateListener;
22import android.os.Bundle;
23import android.util.Log;
24
25import com.android.dialer.DialtactsActivity;
26import 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 */
33public 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 {
58        public boolean isInSearchUi();
59        public boolean hasSearchQuery();
60        public boolean shouldShowActionBar();
61        public int getActionBarHeight();
62        public int getActionBarHideOffset();
63        public void setActionBarHideOffset(int offset);
64    }
65
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     */
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     */
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     */
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     */
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     */
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
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
177    public void setAlpha(float alphaValue) {
178        mSearchBox.animate().alpha(alphaValue).start();
179    }
180
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     */
189    public int getHideOffset() {
190        return mActivityUi.getActionBarHideOffset();
191    }
192
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     */
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     */
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     */
235    public void restoreActionBarOffset() {
236        slideActionBar(mIsActionBarSlidUp /* slideUp */, false /* animate */);
237    }
238
239    @VisibleForTesting
240    public boolean getIsActionBarSlidUp() {
241        return mIsActionBarSlidUp;
242    }
243}
244