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