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