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 android.support.v7.internal.widget;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.os.Build;
22 import android.support.v7.appcompat.R;
23 import android.support.v7.internal.view.menu.MenuBuilder;
24 import android.support.v7.view.ActionMode;
25 import android.support.v7.widget.ActionMenuPresenter;
26 import android.support.v7.widget.ActionMenuView;
27 import android.text.TextUtils;
28 import android.util.AttributeSet;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.accessibility.AccessibilityEvent;
33 import android.widget.LinearLayout;
34 import android.widget.TextView;
35 
36 /**
37  * @hide
38  */
39 public class ActionBarContextView extends AbsActionBarView {
40     private static final String TAG = "ActionBarContextView";
41 
42     private CharSequence mTitle;
43     private CharSequence mSubtitle;
44 
45     private View mClose;
46     private View mCustomView;
47     private LinearLayout mTitleLayout;
48     private TextView mTitleView;
49     private TextView mSubtitleView;
50     private int mTitleStyleRes;
51     private int mSubtitleStyleRes;
52     private boolean mTitleOptional;
53     private int mCloseItemLayout;
54 
ActionBarContextView(Context context)55     public ActionBarContextView(Context context) {
56         this(context, null);
57     }
58 
ActionBarContextView(Context context, AttributeSet attrs)59     public ActionBarContextView(Context context, AttributeSet attrs) {
60         this(context, attrs, R.attr.actionModeStyle);
61     }
62 
ActionBarContextView(Context context, AttributeSet attrs, int defStyle)63     public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
64         super(context, attrs, defStyle);
65 
66         final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
67                 R.styleable.ActionMode, defStyle, 0);
68         setBackgroundDrawable(a.getDrawable(
69                 R.styleable.ActionMode_background));
70         mTitleStyleRes = a.getResourceId(
71                 R.styleable.ActionMode_titleTextStyle, 0);
72         mSubtitleStyleRes = a.getResourceId(
73                 R.styleable.ActionMode_subtitleTextStyle, 0);
74 
75         mContentHeight = a.getLayoutDimension(
76                 R.styleable.ActionMode_height, 0);
77 
78         mCloseItemLayout = a.getResourceId(
79                 R.styleable.ActionMode_closeItemLayout,
80                 R.layout.abc_action_mode_close_item_material);
81 
82         a.recycle();
83     }
84 
85     @Override
onDetachedFromWindow()86     public void onDetachedFromWindow() {
87         super.onDetachedFromWindow();
88         if (mActionMenuPresenter != null) {
89             mActionMenuPresenter.hideOverflowMenu();
90             mActionMenuPresenter.hideSubMenus();
91         }
92     }
93 
setContentHeight(int height)94     public void setContentHeight(int height) {
95         mContentHeight = height;
96     }
97 
setCustomView(View view)98     public void setCustomView(View view) {
99         if (mCustomView != null) {
100             removeView(mCustomView);
101         }
102         mCustomView = view;
103         if (view != null && mTitleLayout != null) {
104             removeView(mTitleLayout);
105             mTitleLayout = null;
106         }
107         if (view != null) {
108             addView(view);
109         }
110         requestLayout();
111     }
112 
setTitle(CharSequence title)113     public void setTitle(CharSequence title) {
114         mTitle = title;
115         initTitle();
116     }
117 
setSubtitle(CharSequence subtitle)118     public void setSubtitle(CharSequence subtitle) {
119         mSubtitle = subtitle;
120         initTitle();
121     }
122 
getTitle()123     public CharSequence getTitle() {
124         return mTitle;
125     }
126 
getSubtitle()127     public CharSequence getSubtitle() {
128         return mSubtitle;
129     }
130 
initTitle()131     private void initTitle() {
132         if (mTitleLayout == null) {
133             LayoutInflater inflater = LayoutInflater.from(getContext());
134             inflater.inflate(R.layout.abc_action_bar_title_item, this);
135             mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
136             mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
137             mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
138             if (mTitleStyleRes != 0) {
139                 mTitleView.setTextAppearance(getContext(), mTitleStyleRes);
140             }
141             if (mSubtitleStyleRes != 0) {
142                 mSubtitleView.setTextAppearance(getContext(), mSubtitleStyleRes);
143             }
144         }
145 
146         mTitleView.setText(mTitle);
147         mSubtitleView.setText(mSubtitle);
148 
149         final boolean hasTitle = !TextUtils.isEmpty(mTitle);
150         final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle);
151         mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE);
152         mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE);
153         if (mTitleLayout.getParent() == null) {
154             addView(mTitleLayout);
155         }
156     }
157 
initForMode(final ActionMode mode)158     public void initForMode(final ActionMode mode) {
159         if (mClose == null) {
160             LayoutInflater inflater = LayoutInflater.from(getContext());
161             mClose = inflater.inflate(mCloseItemLayout, this, false);
162             addView(mClose);
163         } else if (mClose.getParent() == null) {
164             addView(mClose);
165         }
166 
167         View closeButton = mClose.findViewById(R.id.action_mode_close_button);
168         closeButton.setOnClickListener(new OnClickListener() {
169             public void onClick(View v) {
170                 mode.finish();
171             }
172         });
173 
174         final MenuBuilder menu = (MenuBuilder) mode.getMenu();
175         if (mActionMenuPresenter != null) {
176             mActionMenuPresenter.dismissPopupMenus();
177         }
178         mActionMenuPresenter = new ActionMenuPresenter(getContext());
179         mActionMenuPresenter.setReserveOverflow(true);
180 
181         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
182                 LayoutParams.MATCH_PARENT);
183         menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
184         mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
185         mMenuView.setBackgroundDrawable(null);
186         addView(mMenuView, layoutParams);
187     }
188 
closeMode()189     public void closeMode() {
190         if (mClose == null) {
191             killMode();
192             return;
193         }
194     }
195 
killMode()196     public void killMode() {
197         removeAllViews();
198         mCustomView = null;
199         mMenuView = null;
200     }
201 
202     @Override
showOverflowMenu()203     public boolean showOverflowMenu() {
204         if (mActionMenuPresenter != null) {
205             return mActionMenuPresenter.showOverflowMenu();
206         }
207         return false;
208     }
209 
210     @Override
hideOverflowMenu()211     public boolean hideOverflowMenu() {
212         if (mActionMenuPresenter != null) {
213             return mActionMenuPresenter.hideOverflowMenu();
214         }
215         return false;
216     }
217 
218     @Override
isOverflowMenuShowing()219     public boolean isOverflowMenuShowing() {
220         if (mActionMenuPresenter != null) {
221             return mActionMenuPresenter.isOverflowMenuShowing();
222         }
223         return false;
224     }
225 
226     @Override
generateDefaultLayoutParams()227     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
228         // Used by custom views if they don't supply layout params. Everything else
229         // added to an ActionBarContextView should have them already.
230         return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
231     }
232 
233     @Override
generateLayoutParams(AttributeSet attrs)234     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
235         return new MarginLayoutParams(getContext(), attrs);
236     }
237 
238     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)239     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
240         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
241         if (widthMode != MeasureSpec.EXACTLY) {
242             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
243                     "with android:layout_width=\"match_parent\" (or fill_parent)");
244         }
245 
246         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
247         if (heightMode == MeasureSpec.UNSPECIFIED) {
248             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
249                     "with android:layout_height=\"wrap_content\"");
250         }
251 
252         final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
253 
254         int maxHeight = mContentHeight > 0 ?
255                 mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
256 
257         final int verticalPadding = getPaddingTop() + getPaddingBottom();
258         int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
259         final int height = maxHeight - verticalPadding;
260         final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
261 
262         if (mClose != null) {
263             availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0);
264             MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
265             availableWidth -= lp.leftMargin + lp.rightMargin;
266         }
267 
268         if (mMenuView != null && mMenuView.getParent() == this) {
269             availableWidth = measureChildView(mMenuView, availableWidth,
270                     childSpecHeight, 0);
271         }
272 
273         if (mTitleLayout != null && mCustomView == null) {
274             if (mTitleOptional) {
275                 final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
276                 mTitleLayout.measure(titleWidthSpec, childSpecHeight);
277                 final int titleWidth = mTitleLayout.getMeasuredWidth();
278                 final boolean titleFits = titleWidth <= availableWidth;
279                 if (titleFits) {
280                     availableWidth -= titleWidth;
281                 }
282                 mTitleLayout.setVisibility(titleFits ? VISIBLE : GONE);
283             } else {
284                 availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
285             }
286         }
287 
288         if (mCustomView != null) {
289             ViewGroup.LayoutParams lp = mCustomView.getLayoutParams();
290             final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
291                     MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
292             final int customWidth = lp.width >= 0 ?
293                     Math.min(lp.width, availableWidth) : availableWidth;
294             final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
295                     MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
296             final int customHeight = lp.height >= 0 ?
297                     Math.min(lp.height, height) : height;
298             mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
299                     MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
300         }
301 
302         if (mContentHeight <= 0) {
303             int measuredHeight = 0;
304             final int count = getChildCount();
305             for (int i = 0; i < count; i++) {
306                 View v = getChildAt(i);
307                 int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
308                 if (paddedViewHeight > measuredHeight) {
309                     measuredHeight = paddedViewHeight;
310                 }
311             }
312             setMeasuredDimension(contentWidth, measuredHeight);
313         } else {
314             setMeasuredDimension(contentWidth, maxHeight);
315         }
316     }
317 
318     @Override
onLayout(boolean changed, int l, int t, int r, int b)319     protected void onLayout(boolean changed, int l, int t, int r, int b) {
320         final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
321         int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
322         final int y = getPaddingTop();
323         final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
324 
325         if (mClose != null && mClose.getVisibility() != GONE) {
326             MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
327             final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin);
328             final int endMargin = (isLayoutRtl ? lp.leftMargin : lp.rightMargin);
329             x = next(x, startMargin, isLayoutRtl);
330             x += positionChild(mClose, x, y, contentHeight, isLayoutRtl);
331             x = next(x, endMargin, isLayoutRtl);
332         }
333 
334         if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) {
335             x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl);
336         }
337 
338         if (mCustomView != null) {
339             x += positionChild(mCustomView, x, y, contentHeight, isLayoutRtl);
340         }
341 
342         x = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
343 
344         if (mMenuView != null) {
345             x += positionChild(mMenuView, x, y, contentHeight, !isLayoutRtl);
346         }
347     }
348 
349     @Override
shouldDelayChildPressedState()350     public boolean shouldDelayChildPressedState() {
351         return false;
352     }
353 
354     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)355     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
356         if (Build.VERSION.SDK_INT >= 14) {
357             if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
358                 // Action mode started
359                 event.setSource(this);
360                 event.setClassName(getClass().getName());
361                 event.setPackageName(getContext().getPackageName());
362                 event.setContentDescription(mTitle);
363             } else {
364                 super.onInitializeAccessibilityEvent(event);
365             }
366         }
367     }
368 
setTitleOptional(boolean titleOptional)369     public void setTitleOptional(boolean titleOptional) {
370         if (titleOptional != mTitleOptional) {
371             requestLayout();
372         }
373         mTitleOptional = titleOptional;
374     }
375 
isTitleOptional()376     public boolean isTitleOptional() {
377         return mTitleOptional;
378     }
379 }
380