1/*
2 * Copyright (C) 2011 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.internal.widget;
17
18import com.android.internal.R;
19
20import android.util.TypedValue;
21import android.view.ContextThemeWrapper;
22import android.view.MotionEvent;
23import android.widget.ActionMenuPresenter;
24import android.widget.ActionMenuView;
25
26import android.animation.Animator;
27import android.animation.AnimatorSet;
28import android.animation.ObjectAnimator;
29import android.animation.TimeInterpolator;
30import android.content.Context;
31import android.content.res.Configuration;
32import android.content.res.TypedArray;
33import android.util.AttributeSet;
34import android.view.View;
35import android.view.ViewGroup;
36import android.view.animation.DecelerateInterpolator;
37
38public abstract class AbsActionBarView extends ViewGroup {
39    private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
40
41    private static final int FADE_DURATION = 200;
42
43    protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
44
45    /** Context against which to inflate popup menus. */
46    protected final Context mPopupContext;
47
48    protected ActionMenuView mMenuView;
49    protected ActionMenuPresenter mActionMenuPresenter;
50    protected ViewGroup mSplitView;
51    protected boolean mSplitActionBar;
52    protected boolean mSplitWhenNarrow;
53    protected int mContentHeight;
54
55    protected Animator mVisibilityAnim;
56
57    private boolean mEatingTouch;
58    private boolean mEatingHover;
59
60    public AbsActionBarView(Context context) {
61        this(context, null);
62    }
63
64    public AbsActionBarView(Context context, AttributeSet attrs) {
65        this(context, attrs, 0);
66    }
67
68    public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) {
69        this(context, attrs, defStyleAttr, 0);
70    }
71
72    public AbsActionBarView(
73            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
74        super(context, attrs, defStyleAttr, defStyleRes);
75
76        final TypedValue tv = new TypedValue();
77        if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true)
78                && tv.resourceId != 0) {
79            mPopupContext = new ContextThemeWrapper(context, tv.resourceId);
80        } else {
81            mPopupContext = context;
82        }
83    }
84
85    @Override
86    protected void onConfigurationChanged(Configuration newConfig) {
87        super.onConfigurationChanged(newConfig);
88
89        // Action bar can change size on configuration changes.
90        // Reread the desired height from the theme-specified style.
91        TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.ActionBar,
92                com.android.internal.R.attr.actionBarStyle, 0);
93        setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0));
94        a.recycle();
95        if (mSplitWhenNarrow) {
96            setSplitToolbar(getContext().getResources().getBoolean(
97                    com.android.internal.R.bool.split_action_bar_is_narrow));
98        }
99        if (mActionMenuPresenter != null) {
100            mActionMenuPresenter.onConfigurationChanged(newConfig);
101        }
102    }
103
104    @Override
105    public boolean onTouchEvent(MotionEvent ev) {
106        // ActionBarViews always eat touch events, but should still respect the touch event dispatch
107        // contract. If the normal View implementation doesn't want the events, we'll just silently
108        // eat the rest of the gesture without reporting the events to the default implementation
109        // since that's what it expects.
110
111        final int action = ev.getActionMasked();
112        if (action == MotionEvent.ACTION_DOWN) {
113            mEatingTouch = false;
114        }
115
116        if (!mEatingTouch) {
117            final boolean handled = super.onTouchEvent(ev);
118            if (action == MotionEvent.ACTION_DOWN && !handled) {
119                mEatingTouch = true;
120            }
121        }
122
123        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
124            mEatingTouch = false;
125        }
126
127        return true;
128    }
129
130    @Override
131    public boolean onHoverEvent(MotionEvent ev) {
132        // Same deal as onTouchEvent() above. Eat all hover events, but still
133        // respect the touch event dispatch contract.
134
135        final int action = ev.getActionMasked();
136        if (action == MotionEvent.ACTION_HOVER_ENTER) {
137            mEatingHover = false;
138        }
139
140        if (!mEatingHover) {
141            final boolean handled = super.onHoverEvent(ev);
142            if (action == MotionEvent.ACTION_HOVER_ENTER && !handled) {
143                mEatingHover = true;
144            }
145        }
146
147        if (action == MotionEvent.ACTION_HOVER_EXIT
148                || action == MotionEvent.ACTION_CANCEL) {
149            mEatingHover = false;
150        }
151
152        return true;
153    }
154
155    /**
156     * Sets whether the bar should be split right now, no questions asked.
157     * @param split true if the bar should split
158     */
159    public void setSplitToolbar(boolean split) {
160        mSplitActionBar = split;
161    }
162
163    /**
164     * Sets whether the bar should split if we enter a narrow screen configuration.
165     * @param splitWhenNarrow true if the bar should check to split after a config change
166     */
167    public void setSplitWhenNarrow(boolean splitWhenNarrow) {
168        mSplitWhenNarrow = splitWhenNarrow;
169    }
170
171    public void setContentHeight(int height) {
172        mContentHeight = height;
173        requestLayout();
174    }
175
176    public int getContentHeight() {
177        return mContentHeight;
178    }
179
180    public void setSplitView(ViewGroup splitView) {
181        mSplitView = splitView;
182    }
183
184    /**
185     * @return Current visibility or if animating, the visibility being animated to.
186     */
187    public int getAnimatedVisibility() {
188        if (mVisibilityAnim != null) {
189            return mVisAnimListener.mFinalVisibility;
190        }
191        return getVisibility();
192    }
193
194    public Animator setupAnimatorToVisibility(int visibility, long duration) {
195        if (mVisibilityAnim != null) {
196            mVisibilityAnim.cancel();
197        }
198
199        if (visibility == VISIBLE) {
200            if (getVisibility() != VISIBLE) {
201                setAlpha(0);
202                if (mSplitView != null && mMenuView != null) {
203                    mMenuView.setAlpha(0);
204                }
205            }
206            ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 1);
207            anim.setDuration(duration);
208            anim.setInterpolator(sAlphaInterpolator);
209            if (mSplitView != null && mMenuView != null) {
210                AnimatorSet set = new AnimatorSet();
211                ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 1);
212                splitAnim.setDuration(duration);
213                set.addListener(mVisAnimListener.withFinalVisibility(visibility));
214                set.play(anim).with(splitAnim);
215                return set;
216            } else {
217                anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
218                return anim;
219            }
220        } else {
221            ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 0);
222            anim.setDuration(duration);
223            anim.setInterpolator(sAlphaInterpolator);
224            if (mSplitView != null && mMenuView != null) {
225                AnimatorSet set = new AnimatorSet();
226                ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 0);
227                splitAnim.setDuration(duration);
228                set.addListener(mVisAnimListener.withFinalVisibility(visibility));
229                set.play(anim).with(splitAnim);
230                return set;
231            } else {
232                anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
233                return anim;
234            }
235        }
236    }
237
238    public void animateToVisibility(int visibility) {
239        Animator anim = setupAnimatorToVisibility(visibility, FADE_DURATION);
240        anim.start();
241    }
242
243    @Override
244    public void setVisibility(int visibility) {
245        if (visibility != getVisibility()) {
246            if (mVisibilityAnim != null) {
247                mVisibilityAnim.end();
248            }
249            super.setVisibility(visibility);
250        }
251    }
252
253    public boolean showOverflowMenu() {
254        if (mActionMenuPresenter != null) {
255            return mActionMenuPresenter.showOverflowMenu();
256        }
257        return false;
258    }
259
260    public void postShowOverflowMenu() {
261        post(new Runnable() {
262            public void run() {
263                showOverflowMenu();
264            }
265        });
266    }
267
268    public boolean hideOverflowMenu() {
269        if (mActionMenuPresenter != null) {
270            return mActionMenuPresenter.hideOverflowMenu();
271        }
272        return false;
273    }
274
275    public boolean isOverflowMenuShowing() {
276        if (mActionMenuPresenter != null) {
277            return mActionMenuPresenter.isOverflowMenuShowing();
278        }
279        return false;
280    }
281
282    public boolean isOverflowMenuShowPending() {
283        if (mActionMenuPresenter != null) {
284            return mActionMenuPresenter.isOverflowMenuShowPending();
285        }
286        return false;
287    }
288
289    public boolean isOverflowReserved() {
290        return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
291    }
292
293    public boolean canShowOverflowMenu() {
294        return isOverflowReserved() && getVisibility() == VISIBLE;
295    }
296
297    public void dismissPopupMenus() {
298        if (mActionMenuPresenter != null) {
299            mActionMenuPresenter.dismissPopupMenus();
300        }
301    }
302
303    protected int measureChildView(View child, int availableWidth, int childSpecHeight,
304            int spacing) {
305        child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
306                childSpecHeight);
307
308        availableWidth -= child.getMeasuredWidth();
309        availableWidth -= spacing;
310
311        return Math.max(0, availableWidth);
312    }
313
314    static protected int next(int x, int val, boolean isRtl) {
315        return isRtl ? x - val : x + val;
316    }
317
318    protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) {
319        int childWidth = child.getMeasuredWidth();
320        int childHeight = child.getMeasuredHeight();
321        int childTop = y + (contentHeight - childHeight) / 2;
322
323        if (reverse) {
324            child.layout(x - childWidth, childTop, x, childTop + childHeight);
325        } else {
326            child.layout(x, childTop, x + childWidth, childTop + childHeight);
327        }
328
329        return  (reverse ? -childWidth : childWidth);
330    }
331
332    protected class VisibilityAnimListener implements Animator.AnimatorListener {
333        private boolean mCanceled = false;
334        int mFinalVisibility;
335
336        public VisibilityAnimListener withFinalVisibility(int visibility) {
337            mFinalVisibility = visibility;
338            return this;
339        }
340
341        @Override
342        public void onAnimationStart(Animator animation) {
343            setVisibility(VISIBLE);
344            mVisibilityAnim = animation;
345            mCanceled = false;
346        }
347
348        @Override
349        public void onAnimationEnd(Animator animation) {
350            if (mCanceled) return;
351
352            mVisibilityAnim = null;
353            setVisibility(mFinalVisibility);
354            if (mSplitView != null && mMenuView != null) {
355                mMenuView.setVisibility(mFinalVisibility);
356            }
357        }
358
359        @Override
360        public void onAnimationCancel(Animator animation) {
361            mCanceled = true;
362        }
363
364        @Override
365        public void onAnimationRepeat(Animator animation) {
366        }
367    }
368}
369