1 /* 2 * Copyright (C) 2015 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.view; 18 19 import android.annotation.Nullable; 20 import android.app.Notification; 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.Outline; 24 import android.graphics.Rect; 25 import android.graphics.drawable.Drawable; 26 import android.util.AttributeSet; 27 import android.widget.ImageView; 28 import android.widget.RemoteViews; 29 30 import com.android.internal.widget.CachingIconView; 31 32 import java.util.ArrayList; 33 34 /** 35 * A header of a notification view 36 * 37 * @hide 38 */ 39 @RemoteViews.RemoteView 40 public class NotificationHeaderView extends ViewGroup { 41 public static final int NO_COLOR = Notification.COLOR_INVALID; 42 private final int mChildMinWidth; 43 private final int mContentEndMargin; 44 private View mAppName; 45 private View mHeaderText; 46 private OnClickListener mExpandClickListener; 47 private HeaderTouchListener mTouchListener = new HeaderTouchListener(); 48 private ImageView mExpandButton; 49 private CachingIconView mIcon; 50 private View mProfileBadge; 51 private View mInfo; 52 private int mIconColor; 53 private int mOriginalNotificationColor; 54 private boolean mExpanded; 55 private boolean mShowWorkBadgeAtEnd; 56 private Drawable mBackground; 57 private int mHeaderBackgroundHeight; 58 59 ViewOutlineProvider mProvider = new ViewOutlineProvider() { 60 @Override 61 public void getOutline(View view, Outline outline) { 62 if (mBackground != null) { 63 outline.setRect(0, 0, getWidth(), mHeaderBackgroundHeight); 64 outline.setAlpha(1f); 65 } 66 } 67 }; 68 private boolean mAcceptAllTouches; 69 NotificationHeaderView(Context context)70 public NotificationHeaderView(Context context) { 71 this(context, null); 72 } 73 NotificationHeaderView(Context context, @Nullable AttributeSet attrs)74 public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) { 75 this(context, attrs, 0); 76 } 77 NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)78 public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 79 this(context, attrs, defStyleAttr, 0); 80 } 81 NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)82 public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 83 super(context, attrs, defStyleAttr, defStyleRes); 84 mChildMinWidth = getResources().getDimensionPixelSize( 85 com.android.internal.R.dimen.notification_header_shrink_min_width); 86 mContentEndMargin = getResources().getDimensionPixelSize( 87 com.android.internal.R.dimen.notification_content_margin_end); 88 mHeaderBackgroundHeight = getResources().getDimensionPixelSize( 89 com.android.internal.R.dimen.notification_header_background_height); 90 } 91 92 @Override onFinishInflate()93 protected void onFinishInflate() { 94 super.onFinishInflate(); 95 mAppName = findViewById(com.android.internal.R.id.app_name_text); 96 mHeaderText = findViewById(com.android.internal.R.id.header_text); 97 mExpandButton = findViewById(com.android.internal.R.id.expand_button); 98 mIcon = findViewById(com.android.internal.R.id.icon); 99 mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); 100 } 101 102 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)103 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 104 final int givenWidth = MeasureSpec.getSize(widthMeasureSpec); 105 final int givenHeight = MeasureSpec.getSize(heightMeasureSpec); 106 int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth, 107 MeasureSpec.AT_MOST); 108 int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight, 109 MeasureSpec.AT_MOST); 110 int totalWidth = getPaddingStart() + getPaddingEnd(); 111 for (int i = 0; i < getChildCount(); i++) { 112 final View child = getChildAt(i); 113 if (child.getVisibility() == GONE) { 114 // We'll give it the rest of the space in the end 115 continue; 116 } 117 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 118 int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec, 119 lp.leftMargin + lp.rightMargin, lp.width); 120 int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec, 121 lp.topMargin + lp.bottomMargin, lp.height); 122 child.measure(childWidthSpec, childHeightSpec); 123 totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); 124 } 125 if (totalWidth > givenWidth) { 126 int overFlow = totalWidth - givenWidth; 127 // We are overflowing, lets shrink the app name first 128 final int appWidth = mAppName.getMeasuredWidth(); 129 if (overFlow > 0 && mAppName.getVisibility() != GONE && appWidth > mChildMinWidth) { 130 int newSize = appWidth - Math.min(appWidth - mChildMinWidth, overFlow); 131 int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); 132 mAppName.measure(childWidthSpec, wrapContentHeightSpec); 133 overFlow -= appWidth - newSize; 134 } 135 // still overflowing, finaly we shrink the header text 136 if (overFlow > 0 && mHeaderText.getVisibility() != GONE) { 137 // we're still too big 138 final int textWidth = mHeaderText.getMeasuredWidth(); 139 int newSize = Math.max(0, textWidth - overFlow); 140 int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); 141 mHeaderText.measure(childWidthSpec, wrapContentHeightSpec); 142 } 143 } 144 setMeasuredDimension(givenWidth, givenHeight); 145 } 146 147 @Override onLayout(boolean changed, int l, int t, int r, int b)148 protected void onLayout(boolean changed, int l, int t, int r, int b) { 149 int left = getPaddingStart(); 150 int childCount = getChildCount(); 151 int ownHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 152 for (int i = 0; i < childCount; i++) { 153 View child = getChildAt(i); 154 if (child.getVisibility() == GONE) { 155 continue; 156 } 157 int childHeight = child.getMeasuredHeight(); 158 MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); 159 left += params.getMarginStart(); 160 int right = left + child.getMeasuredWidth(); 161 int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f); 162 int bottom = top + childHeight; 163 int layoutLeft = left; 164 int layoutRight = right; 165 if (child == mProfileBadge) { 166 int paddingEnd = getPaddingEnd(); 167 if (mShowWorkBadgeAtEnd) { 168 paddingEnd = mContentEndMargin; 169 } 170 layoutRight = getWidth() - paddingEnd; 171 layoutLeft = layoutRight - child.getMeasuredWidth(); 172 } 173 if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { 174 int ltrLeft = layoutLeft; 175 layoutLeft = getWidth() - layoutRight; 176 layoutRight = getWidth() - ltrLeft; 177 } 178 child.layout(layoutLeft, top, layoutRight, bottom); 179 left = right + params.getMarginEnd(); 180 } 181 updateTouchListener(); 182 } 183 184 @Override generateLayoutParams(AttributeSet attrs)185 public LayoutParams generateLayoutParams(AttributeSet attrs) { 186 return new ViewGroup.MarginLayoutParams(getContext(), attrs); 187 } 188 189 /** 190 * Set a {@link Drawable} to be displayed as a background on the header. 191 */ setHeaderBackgroundDrawable(Drawable drawable)192 public void setHeaderBackgroundDrawable(Drawable drawable) { 193 if (drawable != null) { 194 setWillNotDraw(false); 195 mBackground = drawable; 196 mBackground.setCallback(this); 197 setOutlineProvider(mProvider); 198 } else { 199 setWillNotDraw(true); 200 mBackground = null; 201 setOutlineProvider(null); 202 } 203 invalidate(); 204 } 205 206 @Override onDraw(Canvas canvas)207 protected void onDraw(Canvas canvas) { 208 if (mBackground != null) { 209 mBackground.setBounds(0, 0, getWidth(), mHeaderBackgroundHeight); 210 mBackground.draw(canvas); 211 } 212 } 213 214 @Override verifyDrawable(Drawable who)215 protected boolean verifyDrawable(Drawable who) { 216 return super.verifyDrawable(who) || who == mBackground; 217 } 218 219 @Override drawableStateChanged()220 protected void drawableStateChanged() { 221 if (mBackground != null && mBackground.isStateful()) { 222 mBackground.setState(getDrawableState()); 223 } 224 } 225 updateTouchListener()226 private void updateTouchListener() { 227 if (mExpandClickListener != null) { 228 mTouchListener.bindTouchRects(); 229 } 230 } 231 232 @Override setOnClickListener(@ullable OnClickListener l)233 public void setOnClickListener(@Nullable OnClickListener l) { 234 mExpandClickListener = l; 235 setOnTouchListener(mExpandClickListener != null ? mTouchListener : null); 236 mExpandButton.setOnClickListener(mExpandClickListener); 237 updateTouchListener(); 238 } 239 240 @RemotableViewMethod setOriginalIconColor(int color)241 public void setOriginalIconColor(int color) { 242 mIconColor = color; 243 } 244 getOriginalIconColor()245 public int getOriginalIconColor() { 246 return mIconColor; 247 } 248 249 @RemotableViewMethod setOriginalNotificationColor(int color)250 public void setOriginalNotificationColor(int color) { 251 mOriginalNotificationColor = color; 252 } 253 getOriginalNotificationColor()254 public int getOriginalNotificationColor() { 255 return mOriginalNotificationColor; 256 } 257 258 @RemotableViewMethod setExpanded(boolean expanded)259 public void setExpanded(boolean expanded) { 260 mExpanded = expanded; 261 updateExpandButton(); 262 } 263 updateExpandButton()264 private void updateExpandButton() { 265 int drawableId; 266 int contentDescriptionId; 267 if (mExpanded) { 268 drawableId = com.android.internal.R.drawable.ic_collapse_notification; 269 contentDescriptionId 270 = com.android.internal.R.string.expand_button_content_description_expanded; 271 } else { 272 drawableId = com.android.internal.R.drawable.ic_expand_notification; 273 contentDescriptionId 274 = com.android.internal.R.string.expand_button_content_description_collapsed; 275 } 276 mExpandButton.setImageDrawable(getContext().getDrawable(drawableId)); 277 mExpandButton.setColorFilter(mOriginalNotificationColor); 278 mExpandButton.setContentDescription(mContext.getText(contentDescriptionId)); 279 } 280 setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd)281 public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) { 282 if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) { 283 setClipToPadding(!showWorkBadgeAtEnd); 284 mShowWorkBadgeAtEnd = showWorkBadgeAtEnd; 285 } 286 } 287 getWorkProfileIcon()288 public View getWorkProfileIcon() { 289 return mProfileBadge; 290 } 291 getIcon()292 public CachingIconView getIcon() { 293 return mIcon; 294 } 295 296 public class HeaderTouchListener implements View.OnTouchListener { 297 298 private final ArrayList<Rect> mTouchRects = new ArrayList<>(); 299 private int mTouchSlop; 300 private boolean mTrackGesture; 301 private float mDownX; 302 private float mDownY; 303 HeaderTouchListener()304 public HeaderTouchListener() { 305 } 306 bindTouchRects()307 public void bindTouchRects() { 308 mTouchRects.clear(); 309 addRectAroundViewView(mIcon); 310 addRectAroundViewView(mExpandButton); 311 addWidthRect(); 312 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 313 } 314 addWidthRect()315 private void addWidthRect() { 316 Rect r = new Rect(); 317 r.top = 0; 318 r.bottom = (int) (32 * getResources().getDisplayMetrics().density); 319 r.left = 0; 320 r.right = getWidth(); 321 mTouchRects.add(r); 322 } 323 addRectAroundViewView(View view)324 private void addRectAroundViewView(View view) { 325 final Rect r = getRectAroundView(view); 326 mTouchRects.add(r); 327 } 328 getRectAroundView(View view)329 private Rect getRectAroundView(View view) { 330 float size = 48 * getResources().getDisplayMetrics().density; 331 final Rect r = new Rect(); 332 if (view.getVisibility() == GONE) { 333 view = getFirstChildNotGone(); 334 r.left = (int) (view.getLeft() - size / 2.0f); 335 } else { 336 r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - size / 2.0f); 337 } 338 r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - size / 2.0f); 339 r.bottom = (int) (r.top + size); 340 r.right = (int) (r.left + size); 341 return r; 342 } 343 344 @Override onTouch(View v, MotionEvent event)345 public boolean onTouch(View v, MotionEvent event) { 346 float x = event.getX(); 347 float y = event.getY(); 348 switch (event.getActionMasked() & MotionEvent.ACTION_MASK) { 349 case MotionEvent.ACTION_DOWN: 350 mTrackGesture = false; 351 if (isInside(x, y)) { 352 mDownX = x; 353 mDownY = y; 354 mTrackGesture = true; 355 return true; 356 } 357 break; 358 case MotionEvent.ACTION_MOVE: 359 if (mTrackGesture) { 360 if (Math.abs(mDownX - x) > mTouchSlop 361 || Math.abs(mDownY - y) > mTouchSlop) { 362 mTrackGesture = false; 363 } 364 } 365 break; 366 case MotionEvent.ACTION_UP: 367 if (mTrackGesture) { 368 mExpandButton.performClick(); 369 } 370 break; 371 } 372 return mTrackGesture; 373 } 374 isInside(float x, float y)375 private boolean isInside(float x, float y) { 376 if (mAcceptAllTouches) { 377 return true; 378 } 379 for (int i = 0; i < mTouchRects.size(); i++) { 380 Rect r = mTouchRects.get(i); 381 if (r.contains((int) x, (int) y)) { 382 return true; 383 } 384 } 385 return false; 386 } 387 } 388 getFirstChildNotGone()389 private View getFirstChildNotGone() { 390 for (int i = 0; i < getChildCount(); i++) { 391 final View child = getChildAt(i); 392 if (child.getVisibility() != GONE) { 393 return child; 394 } 395 } 396 return this; 397 } 398 getExpandButton()399 public ImageView getExpandButton() { 400 return mExpandButton; 401 } 402 403 @Override hasOverlappingRendering()404 public boolean hasOverlappingRendering() { 405 return false; 406 } 407 isInTouchRect(float x, float y)408 public boolean isInTouchRect(float x, float y) { 409 if (mExpandClickListener == null) { 410 return false; 411 } 412 return mTouchListener.isInside(x, y); 413 } 414 415 @RemotableViewMethod setAcceptAllTouches(boolean acceptAllTouches)416 public void setAcceptAllTouches(boolean acceptAllTouches) { 417 mAcceptAllTouches = acceptAllTouches; 418 } 419 } 420