Home | History | Annotate | Download | only in view
      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 
     70     public NotificationHeaderView(Context context) {
     71         this(context, null);
     72     }
     73 
     74     public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
     75         this(context, attrs, 0);
     76     }
     77 
     78     public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
     79         this(context, attrs, defStyleAttr, 0);
     80     }
     81 
     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
     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
    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
    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
    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      */
    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
    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
    215     protected boolean verifyDrawable(Drawable who) {
    216         return super.verifyDrawable(who) || who == mBackground;
    217     }
    218 
    219     @Override
    220     protected void drawableStateChanged() {
    221         if (mBackground != null && mBackground.isStateful()) {
    222             mBackground.setState(getDrawableState());
    223         }
    224     }
    225 
    226     private void updateTouchListener() {
    227         if (mExpandClickListener != null) {
    228             mTouchListener.bindTouchRects();
    229         }
    230     }
    231 
    232     @Override
    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
    241     public void setOriginalIconColor(int color) {
    242         mIconColor = color;
    243     }
    244 
    245     public int getOriginalIconColor() {
    246         return mIconColor;
    247     }
    248 
    249     @RemotableViewMethod
    250     public void setOriginalNotificationColor(int color) {
    251         mOriginalNotificationColor = color;
    252     }
    253 
    254     public int getOriginalNotificationColor() {
    255         return mOriginalNotificationColor;
    256     }
    257 
    258     @RemotableViewMethod
    259     public void setExpanded(boolean expanded) {
    260         mExpanded = expanded;
    261         updateExpandButton();
    262     }
    263 
    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 
    281     public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) {
    282         if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) {
    283             setClipToPadding(!showWorkBadgeAtEnd);
    284             mShowWorkBadgeAtEnd = showWorkBadgeAtEnd;
    285         }
    286     }
    287 
    288     public View getWorkProfileIcon() {
    289         return mProfileBadge;
    290     }
    291 
    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 
    304         public HeaderTouchListener() {
    305         }
    306 
    307         public void bindTouchRects() {
    308             mTouchRects.clear();
    309             addRectAroundViewView(mIcon);
    310             addRectAroundViewView(mExpandButton);
    311             addWidthRect();
    312             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    313         }
    314 
    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 
    324         private void addRectAroundViewView(View view) {
    325             final Rect r = getRectAroundView(view);
    326             mTouchRects.add(r);
    327         }
    328 
    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
    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 
    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 
    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 
    399     public ImageView getExpandButton() {
    400         return mExpandButton;
    401     }
    402 
    403     @Override
    404     public boolean hasOverlappingRendering() {
    405         return false;
    406     }
    407 
    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
    416     public void setAcceptAllTouches(boolean acceptAllTouches) {
    417         mAcceptAllTouches = acceptAllTouches;
    418     }
    419 }
    420