Home | History | Annotate | Download | only in notification
      1 /*
      2  * Copyright (C) 2017 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 com.android.launcher3.notification;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorSet;
     21 import android.animation.ObjectAnimator;
     22 import android.app.Notification;
     23 import android.content.Context;
     24 import android.graphics.Rect;
     25 import android.support.annotation.Nullable;
     26 import android.util.AttributeSet;
     27 import android.view.MotionEvent;
     28 import android.view.View;
     29 import android.widget.FrameLayout;
     30 import android.widget.TextView;
     31 
     32 import com.android.launcher3.ItemInfo;
     33 import com.android.launcher3.LauncherAnimUtils;
     34 import com.android.launcher3.R;
     35 import com.android.launcher3.anim.PropertyResetListener;
     36 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
     37 import com.android.launcher3.graphics.IconPalette;
     38 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
     39 import com.android.launcher3.popup.PopupItemView;
     40 import com.android.launcher3.touch.SwipeDetector;
     41 import com.android.launcher3.userevent.nano.LauncherLogProto;
     42 import com.android.launcher3.util.Themes;
     43 
     44 import java.util.List;
     45 
     46 /**
     47  * A {@link FrameLayout} that contains a header, main view and a footer.
     48  * The main view contains the icon and text (title + subtext) of the first notification.
     49  * The footer contains: A list of just the icons of all the notifications past the first one.
     50  * @see NotificationFooterLayout
     51  */
     52 public class NotificationItemView extends PopupItemView implements LogContainerProvider {
     53 
     54     private static final Rect sTempRect = new Rect();
     55 
     56     private TextView mHeaderText;
     57     private TextView mHeaderCount;
     58     private NotificationMainView mMainView;
     59     private NotificationFooterLayout mFooter;
     60     private SwipeDetector mSwipeDetector;
     61     private boolean mAnimatingNextIcon;
     62     private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
     63 
     64     public NotificationItemView(Context context) {
     65         this(context, null, 0);
     66     }
     67 
     68     public NotificationItemView(Context context, AttributeSet attrs) {
     69         this(context, attrs, 0);
     70     }
     71 
     72     public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
     73         super(context, attrs, defStyle);
     74     }
     75 
     76     @Override
     77     protected void onFinishInflate() {
     78         super.onFinishInflate();
     79         mHeaderText = findViewById(R.id.notification_text);
     80         mHeaderCount = findViewById(R.id.notification_count);
     81         mMainView = findViewById(R.id.main_view);
     82         mFooter = findViewById(R.id.footer);
     83 
     84         mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL);
     85         mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
     86         mMainView.setSwipeDetector(mSwipeDetector);
     87     }
     88 
     89     public NotificationMainView getMainView() {
     90         return mMainView;
     91     }
     92 
     93     /**
     94      * This method is used to calculate the height to remove when dismissing the last notification.
     95      * We subtract the height of the footer in this case since the footer should be gone or in the
     96      * process of being removed.
     97      * @return The height of the entire notification item, minus the footer if it still exists.
     98      */
     99     public int getHeightMinusFooter() {
    100         if (mFooter.getParent() == null) {
    101             return getHeight();
    102         }
    103         int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize(
    104                 R.dimen.notification_empty_footer_height);
    105         return getHeight() - excessFooterHeight;
    106     }
    107 
    108     public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
    109         AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
    110 
    111         Rect startRect = new Rect(mPillRect);
    112         Rect endRect = new Rect(mPillRect);
    113         if (shouldRemoveFromTop) {
    114             endRect.top += heightToRemove;
    115         } else {
    116             endRect.bottom -= heightToRemove;
    117         }
    118         anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
    119                 startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
    120 
    121         View bottomGutter = findViewById(R.id.gutter_bottom);
    122         if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
    123             Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
    124                     -heightToRemove);
    125             translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
    126             anim.play(translateGutter);
    127         }
    128 
    129         return anim;
    130     }
    131 
    132     public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
    133         mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount));
    134         if (palette != null) {
    135             if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
    136                 mNotificationHeaderTextColor =
    137                         IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
    138                                 Themes.getAttrColor(getContext(), R.attr.popupColorPrimary));
    139             }
    140             mHeaderText.setTextColor(mNotificationHeaderTextColor);
    141             mHeaderCount.setTextColor(mNotificationHeaderTextColor);
    142         }
    143     }
    144 
    145     @Override
    146     public boolean onInterceptTouchEvent(MotionEvent ev) {
    147         if (mMainView.getNotificationInfo() == null) {
    148             // The notification hasn't been populated yet.
    149             return false;
    150         }
    151         getParent().requestDisallowInterceptTouchEvent(true);
    152         mSwipeDetector.onTouchEvent(ev);
    153         return mSwipeDetector.isDraggingOrSettling();
    154     }
    155 
    156     @Override
    157     public boolean onTouchEvent(MotionEvent ev) {
    158         if (mMainView.getNotificationInfo() == null) {
    159             // The notification hasn't been populated yet.
    160             return false;
    161         }
    162         return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
    163     }
    164 
    165     public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
    166         if (notificationInfos.isEmpty()) {
    167             return;
    168         }
    169 
    170         NotificationInfo mainNotification = notificationInfos.get(0);
    171         mMainView.applyNotificationInfo(mainNotification, mIconView);
    172 
    173         for (int i = 1; i < notificationInfos.size(); i++) {
    174             mFooter.addNotificationInfo(notificationInfos.get(i));
    175         }
    176         mFooter.commitNotificationInfos();
    177     }
    178 
    179     public void trimNotifications(final List<String> notificationKeys) {
    180         boolean dismissedMainNotification = !notificationKeys.contains(
    181                 mMainView.getNotificationInfo().notificationKey);
    182         if (dismissedMainNotification && !mAnimatingNextIcon) {
    183             // Animate the next icon into place as the new main notification.
    184             mAnimatingNextIcon = true;
    185             mMainView.setVisibility(INVISIBLE);
    186             mMainView.setTranslationX(0);
    187             mIconView.getGlobalVisibleRect(sTempRect);
    188             mFooter.animateFirstNotificationTo(sTempRect,
    189                     new NotificationFooterLayout.IconAnimationEndListener() {
    190                 @Override
    191                 public void onIconAnimationEnd(NotificationInfo newMainNotification) {
    192                     if (newMainNotification != null) {
    193                         mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
    194                         mMainView.setVisibility(VISIBLE);
    195                     }
    196                     mAnimatingNextIcon = false;
    197                 }
    198             });
    199         } else {
    200             mFooter.trimNotifications(notificationKeys);
    201         }
    202     }
    203 
    204     @Override
    205     public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
    206             LauncherLogProto.Target targetParent) {
    207         target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
    208         targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
    209     }
    210 }
    211