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