1 /*
2  * Copyright (C) 2010 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 
17 package com.android.internal.widget;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.graphics.Canvas;
23 import android.graphics.ColorFilter;
24 import android.graphics.Outline;
25 import android.graphics.PixelFormat;
26 import android.graphics.drawable.Drawable;
27 import android.util.AttributeSet;
28 import android.view.ActionMode;
29 import android.view.MotionEvent;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.FrameLayout;
33 
34 /**
35  * This class acts as a container for the action bar view and action mode context views.
36  * It applies special styles as needed to help handle animated transitions between them.
37  * @hide
38  */
39 public class ActionBarContainer extends FrameLayout {
40     private boolean mIsTransitioning;
41     private View mTabContainer;
42     private View mActionBarView;
43     private View mActionContextView;
44 
45     private Drawable mBackground;
46     private Drawable mStackedBackground;
47     private Drawable mSplitBackground;
48     private boolean mIsSplit;
49     private boolean mIsStacked;
50     private int mHeight;
51 
ActionBarContainer(Context context)52     public ActionBarContainer(Context context) {
53         this(context, null);
54     }
55 
ActionBarContainer(Context context, AttributeSet attrs)56     public ActionBarContainer(Context context, AttributeSet attrs) {
57         super(context, attrs);
58 
59         // Set a transparent background so that we project appropriately.
60         setBackground(new ActionBarBackgroundDrawable());
61 
62         TypedArray a = context.obtainStyledAttributes(attrs,
63                 com.android.internal.R.styleable.ActionBar);
64         mBackground = a.getDrawable(com.android.internal.R.styleable.ActionBar_background);
65         mStackedBackground = a.getDrawable(
66                 com.android.internal.R.styleable.ActionBar_backgroundStacked);
67         mHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.ActionBar_height, -1);
68 
69         if (getId() == com.android.internal.R.id.split_action_bar) {
70             mIsSplit = true;
71             mSplitBackground = a.getDrawable(
72                     com.android.internal.R.styleable.ActionBar_backgroundSplit);
73         }
74         a.recycle();
75 
76         setWillNotDraw(mIsSplit ? mSplitBackground == null :
77                 mBackground == null && mStackedBackground == null);
78     }
79 
80     @Override
onFinishInflate()81     public void onFinishInflate() {
82         super.onFinishInflate();
83         mActionBarView = findViewById(com.android.internal.R.id.action_bar);
84         mActionContextView = findViewById(com.android.internal.R.id.action_context_bar);
85     }
86 
setPrimaryBackground(Drawable bg)87     public void setPrimaryBackground(Drawable bg) {
88         if (mBackground != null) {
89             mBackground.setCallback(null);
90             unscheduleDrawable(mBackground);
91         }
92         mBackground = bg;
93         if (bg != null) {
94             bg.setCallback(this);
95             if (mActionBarView != null) {
96                 mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
97                         mActionBarView.getRight(), mActionBarView.getBottom());
98             }
99         }
100         setWillNotDraw(mIsSplit ? mSplitBackground == null :
101                 mBackground == null && mStackedBackground == null);
102         invalidate();
103     }
104 
setStackedBackground(Drawable bg)105     public void setStackedBackground(Drawable bg) {
106         if (mStackedBackground != null) {
107             mStackedBackground.setCallback(null);
108             unscheduleDrawable(mStackedBackground);
109         }
110         mStackedBackground = bg;
111         if (bg != null) {
112             bg.setCallback(this);
113             if ((mIsStacked && mStackedBackground != null)) {
114                 mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
115                         mTabContainer.getRight(), mTabContainer.getBottom());
116             }
117         }
118         setWillNotDraw(mIsSplit ? mSplitBackground == null :
119                 mBackground == null && mStackedBackground == null);
120         invalidate();
121     }
122 
setSplitBackground(Drawable bg)123     public void setSplitBackground(Drawable bg) {
124         if (mSplitBackground != null) {
125             mSplitBackground.setCallback(null);
126             unscheduleDrawable(mSplitBackground);
127         }
128         mSplitBackground = bg;
129         if (bg != null) {
130             bg.setCallback(this);
131             if (mIsSplit && mSplitBackground != null) {
132                 mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
133             }
134         }
135         setWillNotDraw(mIsSplit ? mSplitBackground == null :
136                 mBackground == null && mStackedBackground == null);
137         invalidate();
138     }
139 
140     @Override
setVisibility(int visibility)141     public void setVisibility(int visibility) {
142         super.setVisibility(visibility);
143         final boolean isVisible = visibility == VISIBLE;
144         if (mBackground != null) mBackground.setVisible(isVisible, false);
145         if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false);
146         if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false);
147     }
148 
149     @Override
verifyDrawable(@onNull Drawable who)150     protected boolean verifyDrawable(@NonNull Drawable who) {
151         return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) ||
152                 (who == mSplitBackground && mIsSplit) || super.verifyDrawable(who);
153     }
154 
155     @Override
drawableStateChanged()156     protected void drawableStateChanged() {
157         super.drawableStateChanged();
158 
159         final int[] state = getDrawableState();
160         boolean changed = false;
161 
162         final Drawable background = mBackground;
163         if (background != null && background.isStateful()) {
164             changed |= background.setState(state);
165         }
166 
167         final Drawable stackedBackground = mStackedBackground;
168         if (stackedBackground != null && stackedBackground.isStateful()) {
169             changed |= stackedBackground.setState(state);
170         }
171 
172         final Drawable splitBackground = mSplitBackground;
173         if (splitBackground != null && splitBackground.isStateful()) {
174             changed |= splitBackground.setState(state);
175         }
176 
177         if (changed) {
178             invalidate();
179         }
180     }
181 
182     @Override
jumpDrawablesToCurrentState()183     public void jumpDrawablesToCurrentState() {
184         super.jumpDrawablesToCurrentState();
185         if (mBackground != null) {
186             mBackground.jumpToCurrentState();
187         }
188         if (mStackedBackground != null) {
189             mStackedBackground.jumpToCurrentState();
190         }
191         if (mSplitBackground != null) {
192             mSplitBackground.jumpToCurrentState();
193         }
194     }
195 
196     /**
197      * @hide
198      */
199     @Override
onResolveDrawables(int layoutDirection)200     public void onResolveDrawables(int layoutDirection) {
201         super.onResolveDrawables(layoutDirection);
202         if (mBackground != null) {
203             mBackground.setLayoutDirection(layoutDirection);
204         }
205         if (mStackedBackground != null) {
206             mStackedBackground.setLayoutDirection(layoutDirection);
207         }
208         if (mSplitBackground != null) {
209             mSplitBackground.setLayoutDirection(layoutDirection);
210         }
211     }
212 
213     /**
214      * Set the action bar into a "transitioning" state. While transitioning
215      * the bar will block focus and touch from all of its descendants. This
216      * prevents the user from interacting with the bar while it is animating
217      * in or out.
218      *
219      * @param isTransitioning true if the bar is currently transitioning, false otherwise.
220      */
setTransitioning(boolean isTransitioning)221     public void setTransitioning(boolean isTransitioning) {
222         mIsTransitioning = isTransitioning;
223         setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS
224                 : FOCUS_AFTER_DESCENDANTS);
225     }
226 
227     @Override
onInterceptTouchEvent(MotionEvent ev)228     public boolean onInterceptTouchEvent(MotionEvent ev) {
229         return mIsTransitioning || super.onInterceptTouchEvent(ev);
230     }
231 
232     @Override
onTouchEvent(MotionEvent ev)233     public boolean onTouchEvent(MotionEvent ev) {
234         super.onTouchEvent(ev);
235 
236         // An action bar always eats touch events.
237         return true;
238     }
239 
240     @Override
onHoverEvent(MotionEvent ev)241     public boolean onHoverEvent(MotionEvent ev) {
242         super.onHoverEvent(ev);
243 
244         // An action bar always eats hover events.
245         return true;
246     }
247 
setTabContainer(ScrollingTabContainerView tabView)248     public void setTabContainer(ScrollingTabContainerView tabView) {
249         if (mTabContainer != null) {
250             removeView(mTabContainer);
251         }
252         mTabContainer = tabView;
253         if (tabView != null) {
254             addView(tabView);
255             final ViewGroup.LayoutParams lp = tabView.getLayoutParams();
256             lp.width = LayoutParams.MATCH_PARENT;
257             lp.height = LayoutParams.WRAP_CONTENT;
258             tabView.setAllowCollapse(false);
259         }
260     }
261 
getTabContainer()262     public View getTabContainer() {
263         return mTabContainer;
264     }
265 
266     @Override
startActionModeForChild( View child, ActionMode.Callback callback, int type)267     public ActionMode startActionModeForChild(
268             View child, ActionMode.Callback callback, int type) {
269         if (type != ActionMode.TYPE_PRIMARY) {
270             return super.startActionModeForChild(child, callback, type);
271         }
272         return null;
273     }
274 
isCollapsed(View view)275     private static boolean isCollapsed(View view) {
276         return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
277     }
278 
getMeasuredHeightWithMargins(View view)279     private int getMeasuredHeightWithMargins(View view) {
280         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
281         return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
282     }
283 
284     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)285     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
286         if (mActionBarView == null &&
287                 MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) {
288             heightMeasureSpec = MeasureSpec.makeMeasureSpec(
289                     Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST);
290         }
291         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
292 
293         if (mActionBarView == null) return;
294 
295         if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
296             int nonTabMaxHeight = 0;
297             final int childCount = getChildCount();
298             for (int i = 0; i < childCount; i++) {
299                 final View child = getChildAt(i);
300                 if (child == mTabContainer) {
301                     continue;
302                 }
303                 nonTabMaxHeight = Math.max(nonTabMaxHeight, isCollapsed(child) ? 0 :
304                         getMeasuredHeightWithMargins(child));
305             }
306             final int mode = MeasureSpec.getMode(heightMeasureSpec);
307             final int maxHeight = mode == MeasureSpec.AT_MOST ?
308                     MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE;
309             setMeasuredDimension(getMeasuredWidth(),
310                     Math.min(nonTabMaxHeight + getMeasuredHeightWithMargins(mTabContainer),
311                             maxHeight));
312         }
313     }
314 
315     @Override
onLayout(boolean changed, int l, int t, int r, int b)316     public void onLayout(boolean changed, int l, int t, int r, int b) {
317         super.onLayout(changed, l, t, r, b);
318 
319         final View tabContainer = mTabContainer;
320         final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
321 
322         if (tabContainer != null && tabContainer.getVisibility() != GONE) {
323             final int containerHeight = getMeasuredHeight();
324             final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams();
325             final int tabHeight = tabContainer.getMeasuredHeight();
326             tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r,
327                     containerHeight - lp.bottomMargin);
328         }
329 
330         boolean needsInvalidate = false;
331         if (mIsSplit) {
332             if (mSplitBackground != null) {
333                 mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
334                 needsInvalidate = true;
335             }
336         } else {
337             if (mBackground != null) {
338                 if (mActionBarView.getVisibility() == View.VISIBLE) {
339                     mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
340                             mActionBarView.getRight(), mActionBarView.getBottom());
341                 } else if (mActionContextView != null &&
342                         mActionContextView.getVisibility() == View.VISIBLE) {
343                     mBackground.setBounds(mActionContextView.getLeft(), mActionContextView.getTop(),
344                             mActionContextView.getRight(), mActionContextView.getBottom());
345                 } else {
346                     mBackground.setBounds(0, 0, 0, 0);
347                 }
348                 needsInvalidate = true;
349             }
350             mIsStacked = hasTabs;
351             if (hasTabs && mStackedBackground != null) {
352                 mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
353                         tabContainer.getRight(), tabContainer.getBottom());
354                 needsInvalidate = true;
355             }
356         }
357 
358         if (needsInvalidate) {
359             invalidate();
360         }
361     }
362 
363     /**
364      * Dummy drawable so that we don't break background display lists and
365      * projection surfaces.
366      */
367     private class ActionBarBackgroundDrawable extends Drawable {
368         @Override
draw(Canvas canvas)369         public void draw(Canvas canvas) {
370             if (mIsSplit) {
371                 if (mSplitBackground != null) {
372                     mSplitBackground.draw(canvas);
373                 }
374             } else {
375                 if (mBackground != null) {
376                     mBackground.draw(canvas);
377                 }
378                 if (mStackedBackground != null && mIsStacked) {
379                     mStackedBackground.draw(canvas);
380                 }
381             }
382         }
383 
384         @Override
getOutline(@onNull Outline outline)385         public void getOutline(@NonNull Outline outline) {
386             if (mIsSplit) {
387                 if (mSplitBackground != null) {
388                     mSplitBackground.getOutline(outline);
389                 }
390             } else {
391                 // ignore the stacked background for shadow casting
392                 if (mBackground != null) {
393                     mBackground.getOutline(outline);
394                 }
395             }
396         }
397 
398         @Override
setAlpha(int alpha)399         public void setAlpha(int alpha) {
400         }
401 
402         @Override
setColorFilter(ColorFilter colorFilter)403         public void setColorFilter(ColorFilter colorFilter) {
404         }
405 
406         @Override
getOpacity()407         public int getOpacity() {
408             if (mIsSplit) {
409                 if (mSplitBackground != null
410                         && mSplitBackground.getOpacity() == PixelFormat.OPAQUE) {
411                     return PixelFormat.OPAQUE;
412                 }
413             } else {
414                 if (mIsStacked && (mStackedBackground == null
415                         || mStackedBackground.getOpacity() != PixelFormat.OPAQUE)) {
416                     return PixelFormat.UNKNOWN;
417                 }
418                 if (!isCollapsed(mActionBarView) && mBackground != null
419                         && mBackground.getOpacity() == PixelFormat.OPAQUE) {
420                     return PixelFormat.OPAQUE;
421                 }
422             }
423 
424             return PixelFormat.UNKNOWN;
425         }
426     }
427 }
428