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