blob: b2f3f5edcd208ddbde2385a5ca919164f3f5d035 [file] [log] [blame]
Selim Cinek90c8f472015-11-10 17:44:39 -05001/*
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
17package android.view;
18
19import android.annotation.Nullable;
Mathew Inwooda570dee2018-08-17 14:56:00 +010020import android.annotation.UnsupportedAppUsage;
Julia Reynoldsfc640012018-02-21 12:25:27 -050021import android.app.AppOpsManager;
Selim Cinek99104832017-01-25 14:47:33 -080022import android.app.Notification;
Selim Cinek90c8f472015-11-10 17:44:39 -050023import android.content.Context;
Anthony Chen0f6e96c2017-04-07 15:48:17 -070024import android.content.res.Resources;
Lucas Dupinbd9798f2017-10-24 18:04:51 -070025import android.content.res.TypedArray;
Mady Mellorb0a82462016-04-30 17:31:02 -070026import android.graphics.Canvas;
27import android.graphics.Outline;
Selim Cinekaef6c762015-11-20 17:00:18 -080028import android.graphics.Rect;
Mady Mellorb0a82462016-04-30 17:31:02 -070029import android.graphics.drawable.Drawable;
Julia Reynoldsfc640012018-02-21 12:25:27 -050030import android.util.ArraySet;
Selim Cinek90c8f472015-11-10 17:44:39 -050031import android.util.AttributeSet;
Selim Cinek7b836392015-12-04 20:02:59 -080032import android.widget.ImageView;
Selim Cinek90c8f472015-11-10 17:44:39 -050033import android.widget.RemoteViews;
34
Anthony Chen0f6e96c2017-04-07 15:48:17 -070035import com.android.internal.R;
Selim Cinek0242fbb2016-10-19 13:38:32 -070036import com.android.internal.widget.CachingIconView;
37
Selim Cinekaef6c762015-11-20 17:00:18 -080038import java.util.ArrayList;
Selim Cinek90c8f472015-11-10 17:44:39 -050039
40/**
41 * A header of a notification view
42 *
43 * @hide
44 */
45@RemoteViews.RemoteView
Selim Cinekcb445682016-01-29 16:13:12 -080046public class NotificationHeaderView extends ViewGroup {
Selim Cinek99104832017-01-25 14:47:33 -080047 public static final int NO_COLOR = Notification.COLOR_INVALID;
Selim Cinek413142a2016-02-03 10:58:13 -080048 private final int mChildMinWidth;
Selim Cinek6ecc8102016-01-26 18:26:19 -080049 private final int mContentEndMargin;
Lucas Dupinbd9798f2017-10-24 18:04:51 -070050 private final int mGravity;
Selim Cinek90c8f472015-11-10 17:44:39 -050051 private View mAppName;
Selim Cinek0f9dd1e2016-04-05 17:03:40 -070052 private View mHeaderText;
Selim Cinekafeed292017-12-12 17:32:44 -080053 private View mSecondaryHeaderText;
Selim Cinekaef6c762015-11-20 17:00:18 -080054 private OnClickListener mExpandClickListener;
Julia Reynoldsb5867452018-02-28 16:31:35 -050055 private OnClickListener mAppOpsListener;
Selim Cinekaef6c762015-11-20 17:00:18 -080056 private HeaderTouchListener mTouchListener = new HeaderTouchListener();
Selim Cinek7b836392015-12-04 20:02:59 -080057 private ImageView mExpandButton;
Selim Cinek0242fbb2016-10-19 13:38:32 -070058 private CachingIconView mIcon;
Selim Cinekc848c3a2016-01-13 15:27:30 -080059 private View mProfileBadge;
Julia Reynoldsfc640012018-02-21 12:25:27 -050060 private View mOverlayIcon;
61 private View mCameraIcon;
62 private View mMicIcon;
63 private View mAppOps;
Gus Prevasa3226492018-10-23 11:10:09 -040064 private View mAudiblyAlertedIcon;
Selim Cinekea4bef72015-12-02 15:51:10 -080065 private int mIconColor;
66 private int mOriginalNotificationColor;
Selim Cinek7b836392015-12-04 20:02:59 -080067 private boolean mExpanded;
Anthony Chen0f6e96c2017-04-07 15:48:17 -070068 private boolean mShowExpandButtonAtEnd;
Selim Cinek6ecc8102016-01-26 18:26:19 -080069 private boolean mShowWorkBadgeAtEnd;
Beth Thibodeau837bfc22019-02-19 12:26:53 -050070 private int mHeaderTextMarginEnd;
Mady Mellorb0a82462016-04-30 17:31:02 -070071 private Drawable mBackground;
Anthony Chen0f6e96c2017-04-07 15:48:17 -070072 private boolean mEntireHeaderClickable;
Selim Cinek499c20f2017-07-20 14:06:09 -070073 private boolean mExpandOnlyOnButton;
Anthony Chen0f6e96c2017-04-07 15:48:17 -070074 private boolean mAcceptAllTouches;
Lucas Dupinbd9798f2017-10-24 18:04:51 -070075 private int mTotalWidth;
Mady Mellorb0a82462016-04-30 17:31:02 -070076
77 ViewOutlineProvider mProvider = new ViewOutlineProvider() {
78 @Override
79 public void getOutline(View view, Outline outline) {
80 if (mBackground != null) {
Selim Cinekafeed292017-12-12 17:32:44 -080081 outline.setRect(0, 0, getWidth(), getHeight());
Mady Mellorb0a82462016-04-30 17:31:02 -070082 outline.setAlpha(1f);
83 }
84 }
85 };
Selim Cinek90c8f472015-11-10 17:44:39 -050086
87 public NotificationHeaderView(Context context) {
88 this(context, null);
89 }
90
Mathew Inwooda570dee2018-08-17 14:56:00 +010091 @UnsupportedAppUsage
Selim Cinek90c8f472015-11-10 17:44:39 -050092 public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
93 this(context, attrs, 0);
94 }
95
96 public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
97 this(context, attrs, defStyleAttr, 0);
98 }
99
100 public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
101 super(context, attrs, defStyleAttr, defStyleRes);
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700102 Resources res = getResources();
103 mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
104 mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700105 mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
Lucas Dupinbd9798f2017-10-24 18:04:51 -0700106
107 int[] attrIds = { android.R.attr.gravity };
108 TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
109 mGravity = ta.getInt(0, 0);
110 ta.recycle();
Selim Cinek90c8f472015-11-10 17:44:39 -0500111 }
112
113 @Override
114 protected void onFinishInflate() {
115 super.onFinishInflate();
116 mAppName = findViewById(com.android.internal.R.id.app_name_text);
Selim Cinek0f9dd1e2016-04-05 17:03:40 -0700117 mHeaderText = findViewById(com.android.internal.R.id.header_text);
Selim Cinekafeed292017-12-12 17:32:44 -0800118 mSecondaryHeaderText = findViewById(com.android.internal.R.id.header_text_secondary);
Alan Viverette51efddb2017-04-05 10:00:01 -0400119 mExpandButton = findViewById(com.android.internal.R.id.expand_button);
120 mIcon = findViewById(com.android.internal.R.id.icon);
Selim Cinekc848c3a2016-01-13 15:27:30 -0800121 mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
Julia Reynoldsfc640012018-02-21 12:25:27 -0500122 mCameraIcon = findViewById(com.android.internal.R.id.camera);
123 mMicIcon = findViewById(com.android.internal.R.id.mic);
124 mOverlayIcon = findViewById(com.android.internal.R.id.overlay);
125 mAppOps = findViewById(com.android.internal.R.id.app_ops);
Gus Prevasa3226492018-10-23 11:10:09 -0400126 mAudiblyAlertedIcon = findViewById(com.android.internal.R.id.alerted_icon);
Selim Cinek90c8f472015-11-10 17:44:39 -0500127 }
128
129 @Override
130 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
131 final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
132 final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
133 int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
134 MeasureSpec.AT_MOST);
135 int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
136 MeasureSpec.AT_MOST);
Beth Thibodeau837bfc22019-02-19 12:26:53 -0500137 int totalWidth = getPaddingStart();
138 int iconWidth = getPaddingEnd();
Selim Cinek90c8f472015-11-10 17:44:39 -0500139 for (int i = 0; i < getChildCount(); i++) {
140 final View child = getChildAt(i);
141 if (child.getVisibility() == GONE) {
142 // We'll give it the rest of the space in the end
143 continue;
144 }
145 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
146 int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
147 lp.leftMargin + lp.rightMargin, lp.width);
148 int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
149 lp.topMargin + lp.bottomMargin, lp.height);
150 child.measure(childWidthSpec, childHeightSpec);
Beth Thibodeau837bfc22019-02-19 12:26:53 -0500151 if ((child == mExpandButton && mShowExpandButtonAtEnd)
152 || child == mProfileBadge
153 || child == mAppOps) {
154 iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
155 } else {
156 totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
157 }
Selim Cinek90c8f472015-11-10 17:44:39 -0500158 }
Beth Thibodeau837bfc22019-02-19 12:26:53 -0500159
160 // Ensure that there is at least enough space for the icons
161 int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth);
162 if (totalWidth > givenWidth - endMargin) {
163 int overFlow = totalWidth - givenWidth + endMargin;
Selim Cinek0f9dd1e2016-04-05 17:03:40 -0700164 // We are overflowing, lets shrink the app name first
Selim Cinekafeed292017-12-12 17:32:44 -0800165 overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
166 mChildMinWidth);
167
168 // still overflowing, we shrink the header text
169 overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
170
171 // still overflowing, finally we shrink the secondary header text
172 shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
173 0);
Selim Cinek90c8f472015-11-10 17:44:39 -0500174 }
Beth Thibodeau837bfc22019-02-19 12:26:53 -0500175 totalWidth += getPaddingEnd();
Lucas Dupinbd9798f2017-10-24 18:04:51 -0700176 mTotalWidth = Math.min(totalWidth, givenWidth);
Selim Cinek4c4c7382016-02-03 16:17:09 -0800177 setMeasuredDimension(givenWidth, givenHeight);
Selim Cinek90c8f472015-11-10 17:44:39 -0500178 }
Selim Cinekaef6c762015-11-20 17:00:18 -0800179
Selim Cinekafeed292017-12-12 17:32:44 -0800180 private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
181 int minimumWidth) {
182 final int oldWidth = targetView.getMeasuredWidth();
183 if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
184 // we're still too big
185 int newSize = Math.max(minimumWidth, oldWidth - overFlow);
186 int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
187 targetView.measure(childWidthSpec, heightSpec);
188 overFlow -= oldWidth - newSize;
189 }
190 return overFlow;
191 }
192
Selim Cinekaef6c762015-11-20 17:00:18 -0800193 @Override
194 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinekcb445682016-01-29 16:13:12 -0800195 int left = getPaddingStart();
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700196 int end = getMeasuredWidth();
Lucas Dupinbd9798f2017-10-24 18:04:51 -0700197 final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
198 if (centerAligned) {
199 left += getMeasuredWidth() / 2 - mTotalWidth / 2;
200 }
Selim Cinekcb445682016-01-29 16:13:12 -0800201 int childCount = getChildCount();
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700202 int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
Selim Cinekcb445682016-01-29 16:13:12 -0800203 for (int i = 0; i < childCount; i++) {
204 View child = getChildAt(i);
205 if (child.getVisibility() == GONE) {
206 continue;
207 }
208 int childHeight = child.getMeasuredHeight();
209 MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
Gus Prevasc79816b2018-12-27 15:46:00 -0500210 int layoutLeft;
211 int layoutRight;
Selim Cinekcb445682016-01-29 16:13:12 -0800212 int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
213 int bottom = top + childHeight;
Gus Prevas87717612018-12-17 14:00:12 -0500214 if ((child == mExpandButton && mShowExpandButtonAtEnd)
215 || child == mProfileBadge
216 || child == mAppOps) {
217 if (end == getMeasuredWidth()) {
218 layoutRight = end - mContentEndMargin;
219 } else {
220 layoutRight = end - params.getMarginEnd();
Selim Cinekcb445682016-01-29 16:13:12 -0800221 }
Gus Prevas87717612018-12-17 14:00:12 -0500222 layoutLeft = layoutRight - child.getMeasuredWidth();
223 end = layoutLeft - params.getMarginStart();
Gus Prevasc79816b2018-12-27 15:46:00 -0500224 } else {
225 left += params.getMarginStart();
226 int right = left + child.getMeasuredWidth();
227 layoutLeft = left;
228 layoutRight = right;
229 left = right + params.getMarginEnd();
Julia Reynoldsfc640012018-02-21 12:25:27 -0500230 }
Selim Cinekc848c3a2016-01-13 15:27:30 -0800231 if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
Selim Cinekcb445682016-01-29 16:13:12 -0800232 int ltrLeft = layoutLeft;
233 layoutLeft = getWidth() - layoutRight;
234 layoutRight = getWidth() - ltrLeft;
Selim Cinekc848c3a2016-01-13 15:27:30 -0800235 }
Selim Cinekcb445682016-01-29 16:13:12 -0800236 child.layout(layoutLeft, top, layoutRight, bottom);
Selim Cinekc848c3a2016-01-13 15:27:30 -0800237 }
Selim Cinekaef6c762015-11-20 17:00:18 -0800238 updateTouchListener();
239 }
240
Selim Cinekcb445682016-01-29 16:13:12 -0800241 @Override
242 public LayoutParams generateLayoutParams(AttributeSet attrs) {
243 return new ViewGroup.MarginLayoutParams(getContext(), attrs);
244 }
245
Mady Mellorb0a82462016-04-30 17:31:02 -0700246 /**
247 * Set a {@link Drawable} to be displayed as a background on the header.
248 */
249 public void setHeaderBackgroundDrawable(Drawable drawable) {
250 if (drawable != null) {
251 setWillNotDraw(false);
252 mBackground = drawable;
253 mBackground.setCallback(this);
254 setOutlineProvider(mProvider);
255 } else {
256 setWillNotDraw(true);
257 mBackground = null;
258 setOutlineProvider(null);
259 }
260 invalidate();
261 }
262
263 @Override
264 protected void onDraw(Canvas canvas) {
265 if (mBackground != null) {
Selim Cinekafeed292017-12-12 17:32:44 -0800266 mBackground.setBounds(0, 0, getWidth(), getHeight());
Mady Mellorb0a82462016-04-30 17:31:02 -0700267 mBackground.draw(canvas);
268 }
269 }
270
271 @Override
272 protected boolean verifyDrawable(Drawable who) {
273 return super.verifyDrawable(who) || who == mBackground;
274 }
275
276 @Override
277 protected void drawableStateChanged() {
278 if (mBackground != null && mBackground.isStateful()) {
279 mBackground.setState(getDrawableState());
280 }
281 }
282
Selim Cinekaef6c762015-11-20 17:00:18 -0800283 private void updateTouchListener() {
Julia Reynoldsb5867452018-02-28 16:31:35 -0500284 if (mExpandClickListener == null && mAppOpsListener == null) {
285 setOnTouchListener(null);
286 return;
Selim Cinekaef6c762015-11-20 17:00:18 -0800287 }
Julia Reynoldsb5867452018-02-28 16:31:35 -0500288 setOnTouchListener(mTouchListener);
289 mTouchListener.bindTouchRects();
290 }
291
292 /**
293 * Sets onclick listener for app ops icons.
294 */
295 public void setAppOpsOnClickListener(OnClickListener l) {
296 mAppOpsListener = l;
297 mAppOps.setOnClickListener(mAppOpsListener);
Dieter Hsu3a9b5a92018-05-15 19:02:52 +0800298 mCameraIcon.setOnClickListener(mAppOpsListener);
299 mMicIcon.setOnClickListener(mAppOpsListener);
300 mOverlayIcon.setOnClickListener(mAppOpsListener);
Julia Reynoldsb5867452018-02-28 16:31:35 -0500301 updateTouchListener();
Selim Cinekaef6c762015-11-20 17:00:18 -0800302 }
303
304 @Override
305 public void setOnClickListener(@Nullable OnClickListener l) {
306 mExpandClickListener = l;
Selim Cineke9bad242016-06-15 11:46:37 -0700307 mExpandButton.setOnClickListener(mExpandClickListener);
Selim Cinekaef6c762015-11-20 17:00:18 -0800308 updateTouchListener();
309 }
310
Selim Cinekea4bef72015-12-02 15:51:10 -0800311 @RemotableViewMethod
312 public void setOriginalIconColor(int color) {
313 mIconColor = color;
314 }
315
316 public int getOriginalIconColor() {
317 return mIconColor;
318 }
319
320 @RemotableViewMethod
321 public void setOriginalNotificationColor(int color) {
322 mOriginalNotificationColor = color;
323 }
324
325 public int getOriginalNotificationColor() {
326 return mOriginalNotificationColor;
327 }
328
Selim Cinek7b836392015-12-04 20:02:59 -0800329 @RemotableViewMethod
330 public void setExpanded(boolean expanded) {
331 mExpanded = expanded;
332 updateExpandButton();
333 }
334
Julia Reynoldsfc640012018-02-21 12:25:27 -0500335 /**
336 * Shows or hides 'app op in use' icons based on app usage.
337 */
338 public void showAppOpsIcons(ArraySet<Integer> appOps) {
Julia Reynoldsb5867452018-02-28 16:31:35 -0500339 if (mOverlayIcon == null || mCameraIcon == null || mMicIcon == null || appOps == null) {
Julia Reynoldsfc640012018-02-21 12:25:27 -0500340 return;
341 }
342
343 mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
344 ? View.VISIBLE : View.GONE);
345 mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
346 ? View.VISIBLE : View.GONE);
347 mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
348 ? View.VISIBLE : View.GONE);
349 }
350
Gus Prevasa3226492018-10-23 11:10:09 -0400351 /** Updates icon visibility based on the noisiness of the notification. */
Gus Prevas7306b902018-12-11 10:57:06 -0500352 public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
Gus Prevasa3226492018-10-23 11:10:09 -0400353 mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
354 }
355
Selim Cinek7b836392015-12-04 20:02:59 -0800356 private void updateExpandButton() {
357 int drawableId;
Selim Cinekc0ac4af2017-03-03 15:13:48 -0800358 int contentDescriptionId;
Selim Cinek6db57582016-03-04 19:11:27 -0800359 if (mExpanded) {
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700360 drawableId = R.drawable.ic_collapse_notification;
361 contentDescriptionId = R.string.expand_button_content_description_expanded;
Selim Cinek7b836392015-12-04 20:02:59 -0800362 } else {
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700363 drawableId = R.drawable.ic_expand_notification;
364 contentDescriptionId = R.string.expand_button_content_description_collapsed;
Selim Cinek7b836392015-12-04 20:02:59 -0800365 }
366 mExpandButton.setImageDrawable(getContext().getDrawable(drawableId));
367 mExpandButton.setColorFilter(mOriginalNotificationColor);
Selim Cinekc0ac4af2017-03-03 15:13:48 -0800368 mExpandButton.setContentDescription(mContext.getText(contentDescriptionId));
Selim Cinek7b836392015-12-04 20:02:59 -0800369 }
370
Selim Cinek6ecc8102016-01-26 18:26:19 -0800371 public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) {
372 if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) {
373 setClipToPadding(!showWorkBadgeAtEnd);
374 mShowWorkBadgeAtEnd = showWorkBadgeAtEnd;
375 }
376 }
377
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700378 /**
379 * Sets whether or not the expand button appears at the end of the NotificationHeaderView. If
380 * both this and {@link #setShowWorkBadgeAtEnd(boolean)} have been set to true, then the
381 * expand button will appear closer to the end than the work badge.
382 */
383 public void setShowExpandButtonAtEnd(boolean showExpandButtonAtEnd) {
384 if (showExpandButtonAtEnd != mShowExpandButtonAtEnd) {
385 setClipToPadding(!showExpandButtonAtEnd);
386 mShowExpandButtonAtEnd = showExpandButtonAtEnd;
387 }
388 }
389
Selim Cinek0d07c7e2016-01-27 18:38:31 -0800390 public View getWorkProfileIcon() {
391 return mProfileBadge;
392 }
393
Selim Cinek0242fbb2016-10-19 13:38:32 -0700394 public CachingIconView getIcon() {
Selim Cinek281c2022016-10-13 19:14:43 -0700395 return mIcon;
396 }
397
Beth Thibodeau837bfc22019-02-19 12:26:53 -0500398 /**
399 * Sets the margin end for the text portion of the header, excluding right-aligned elements
400 * @param headerTextMarginEnd margin size
401 */
402 @RemotableViewMethod
403 public void setHeaderTextMarginEnd(int headerTextMarginEnd) {
404 if (mHeaderTextMarginEnd != headerTextMarginEnd) {
405 mHeaderTextMarginEnd = headerTextMarginEnd;
406 requestLayout();
407 }
408 }
409
410 /**
411 * Get the current margin end value for the header text
412 * @return margin size
413 */
414 public int getHeaderTextMarginEnd() {
415 return mHeaderTextMarginEnd;
416 }
417
Selim Cinekaef6c762015-11-20 17:00:18 -0800418 public class HeaderTouchListener implements View.OnTouchListener {
419
420 private final ArrayList<Rect> mTouchRects = new ArrayList<>();
Selim Cinek499c20f2017-07-20 14:06:09 -0700421 private Rect mExpandButtonRect;
Julia Reynoldsb5867452018-02-28 16:31:35 -0500422 private Rect mAppOpsRect;
Selim Cinekaef6c762015-11-20 17:00:18 -0800423 private int mTouchSlop;
424 private boolean mTrackGesture;
425 private float mDownX;
426 private float mDownY;
427
428 public HeaderTouchListener() {
429 }
430
431 public void bindTouchRects() {
432 mTouchRects.clear();
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700433 addRectAroundView(mIcon);
Selim Cinek499c20f2017-07-20 14:06:09 -0700434 mExpandButtonRect = addRectAroundView(mExpandButton);
Julia Reynoldsb5867452018-02-28 16:31:35 -0500435 mAppOpsRect = addRectAroundView(mAppOps);
Selim Cinek4c4c7382016-02-03 16:17:09 -0800436 addWidthRect();
Selim Cinekaef6c762015-11-20 17:00:18 -0800437 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
438 }
439
Selim Cinek4c4c7382016-02-03 16:17:09 -0800440 private void addWidthRect() {
441 Rect r = new Rect();
Selim Cinekaef6c762015-11-20 17:00:18 -0800442 r.top = 0;
443 r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
Selim Cinek4c4c7382016-02-03 16:17:09 -0800444 r.left = 0;
445 r.right = getWidth();
Selim Cinekaef6c762015-11-20 17:00:18 -0800446 mTouchRects.add(r);
447 }
448
Selim Cinek499c20f2017-07-20 14:06:09 -0700449 private Rect addRectAroundView(View view) {
Selim Cinekaef6c762015-11-20 17:00:18 -0800450 final Rect r = getRectAroundView(view);
451 mTouchRects.add(r);
Selim Cinek499c20f2017-07-20 14:06:09 -0700452 return r;
Selim Cinekaef6c762015-11-20 17:00:18 -0800453 }
454
455 private Rect getRectAroundView(View view) {
456 float size = 48 * getResources().getDisplayMetrics().density;
Julia Reynoldsb5867452018-02-28 16:31:35 -0500457 float width = Math.max(size, view.getWidth());
458 float height = Math.max(size, view.getHeight());
Selim Cinekaef6c762015-11-20 17:00:18 -0800459 final Rect r = new Rect();
460 if (view.getVisibility() == GONE) {
461 view = getFirstChildNotGone();
Julia Reynoldsb5867452018-02-28 16:31:35 -0500462 r.left = (int) (view.getLeft() - width / 2.0f);
Selim Cinekaef6c762015-11-20 17:00:18 -0800463 } else {
Julia Reynoldsb5867452018-02-28 16:31:35 -0500464 r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f);
Selim Cinekaef6c762015-11-20 17:00:18 -0800465 }
Julia Reynoldsb5867452018-02-28 16:31:35 -0500466 r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f);
467 r.bottom = (int) (r.top + height);
468 r.right = (int) (r.left + width);
Selim Cinekaef6c762015-11-20 17:00:18 -0800469 return r;
470 }
471
472 @Override
473 public boolean onTouch(View v, MotionEvent event) {
474 float x = event.getX();
475 float y = event.getY();
476 switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
477 case MotionEvent.ACTION_DOWN:
478 mTrackGesture = false;
479 if (isInside(x, y)) {
Selim Cinek1b554392017-02-28 17:22:49 -0800480 mDownX = x;
481 mDownY = y;
Selim Cinekaef6c762015-11-20 17:00:18 -0800482 mTrackGesture = true;
483 return true;
484 }
485 break;
486 case MotionEvent.ACTION_MOVE:
487 if (mTrackGesture) {
488 if (Math.abs(mDownX - x) > mTouchSlop
489 || Math.abs(mDownY - y) > mTouchSlop) {
490 mTrackGesture = false;
491 }
492 }
493 break;
494 case MotionEvent.ACTION_UP:
495 if (mTrackGesture) {
Julia Reynoldsb5867452018-02-28 16:31:35 -0500496 if (mAppOps.isVisibleToUser() && (mAppOpsRect.contains((int) x, (int) y)
497 || mAppOpsRect.contains((int) mDownX, (int) mDownY))) {
498 mAppOps.performClick();
499 return true;
500 }
Selim Cinekc0ac4af2017-03-03 15:13:48 -0800501 mExpandButton.performClick();
Selim Cinekaef6c762015-11-20 17:00:18 -0800502 }
503 break;
504 }
505 return mTrackGesture;
506 }
507
508 private boolean isInside(float x, float y) {
Selim Cinek1b554392017-02-28 17:22:49 -0800509 if (mAcceptAllTouches) {
510 return true;
511 }
Selim Cinek499c20f2017-07-20 14:06:09 -0700512 if (mExpandOnlyOnButton) {
513 return mExpandButtonRect.contains((int) x, (int) y);
514 }
Selim Cinekaef6c762015-11-20 17:00:18 -0800515 for (int i = 0; i < mTouchRects.size(); i++) {
516 Rect r = mTouchRects.get(i);
517 if (r.contains((int) x, (int) y)) {
Selim Cinekaef6c762015-11-20 17:00:18 -0800518 return true;
519 }
520 }
521 return false;
522 }
523 }
524
525 private View getFirstChildNotGone() {
526 for (int i = 0; i < getChildCount(); i++) {
527 final View child = getChildAt(i);
528 if (child.getVisibility() != GONE) {
529 return child;
530 }
531 }
532 return this;
533 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100534
535 public ImageView getExpandButton() {
536 return mExpandButton;
537 }
538
539 @Override
540 public boolean hasOverlappingRendering() {
541 return false;
542 }
Selim Cinek6183d122016-01-14 18:48:41 -0800543
544 public boolean isInTouchRect(float x, float y) {
545 if (mExpandClickListener == null) {
546 return false;
547 }
548 return mTouchListener.isInside(x, y);
549 }
Selim Cinek1b554392017-02-28 17:22:49 -0800550
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700551 /**
552 * Sets whether or not all touches to this header view will register as a click. Note that
553 * if the config value for {@code config_notificationHeaderClickableForExpand} is {@code true},
554 * then calling this method with {@code false} will not override that configuration.
555 */
Selim Cinek1b554392017-02-28 17:22:49 -0800556 @RemotableViewMethod
557 public void setAcceptAllTouches(boolean acceptAllTouches) {
Anthony Chen0f6e96c2017-04-07 15:48:17 -0700558 mAcceptAllTouches = mEntireHeaderClickable || acceptAllTouches;
Selim Cinek1b554392017-02-28 17:22:49 -0800559 }
Selim Cinek499c20f2017-07-20 14:06:09 -0700560
561 /**
562 * Sets whether only the expand icon itself should serve as the expand target.
563 */
564 @RemotableViewMethod
565 public void setExpandOnlyOnButton(boolean expandOnlyOnButton) {
566 mExpandOnlyOnButton = expandOnlyOnButton;
567 }
Selim Cinek90c8f472015-11-10 17:44:39 -0500568}