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