Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2018 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.systemui.statusbar.phone;
     18 
     19 import android.graphics.Point;
     20 import android.graphics.Rect;
     21 import android.view.View;
     22 import android.view.WindowInsets;
     23 
     24 import com.android.internal.annotations.VisibleForTesting;
     25 import com.android.systemui.Dependency;
     26 import com.android.systemui.R;
     27 import com.android.systemui.statusbar.CrossFadeHelper;
     28 import com.android.systemui.statusbar.ExpandableNotificationRow;
     29 import com.android.systemui.statusbar.HeadsUpStatusBarView;
     30 import com.android.systemui.statusbar.NotificationData;
     31 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
     32 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
     33 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     34 
     35 import java.util.function.BiConsumer;
     36 import java.util.function.Consumer;
     37 
     38 /**
     39  * Controls the appearance of heads up notifications in the icon area and the header itself.
     40  */
     41 public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
     42         DarkIconDispatcher.DarkReceiver {
     43     public static final int CONTENT_FADE_DURATION = 110;
     44     public static final int CONTENT_FADE_DELAY = 100;
     45     private final NotificationIconAreaController mNotificationIconAreaController;
     46     private final HeadsUpManagerPhone mHeadsUpManager;
     47     private final NotificationStackScrollLayout mStackScroller;
     48     private final HeadsUpStatusBarView mHeadsUpStatusBarView;
     49     private final View mClockView;
     50     private final DarkIconDispatcher mDarkIconDispatcher;
     51     private final NotificationPanelView mPanelView;
     52     private final Consumer<ExpandableNotificationRow>
     53             mSetTrackingHeadsUp = this::setTrackingHeadsUp;
     54     private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
     55     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setExpandedHeight;
     56     private float mExpandedHeight;
     57     private boolean mIsExpanded;
     58     private float mExpandFraction;
     59     private ExpandableNotificationRow mTrackedChild;
     60     private boolean mShown;
     61     private final View.OnLayoutChangeListener mStackScrollLayoutChangeListener =
     62             (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
     63                     -> updatePanelTranslation();
     64     Point mPoint;
     65 
     66     public HeadsUpAppearanceController(
     67             NotificationIconAreaController notificationIconAreaController,
     68             HeadsUpManagerPhone headsUpManager,
     69             View statusbarView) {
     70         this(notificationIconAreaController, headsUpManager,
     71                 statusbarView.findViewById(R.id.heads_up_status_bar_view),
     72                 statusbarView.findViewById(R.id.notification_stack_scroller),
     73                 statusbarView.findViewById(R.id.notification_panel),
     74                 statusbarView.findViewById(R.id.clock));
     75     }
     76 
     77     @VisibleForTesting
     78     public HeadsUpAppearanceController(
     79             NotificationIconAreaController notificationIconAreaController,
     80             HeadsUpManagerPhone headsUpManager,
     81             HeadsUpStatusBarView headsUpStatusBarView,
     82             NotificationStackScrollLayout stackScroller,
     83             NotificationPanelView panelView,
     84             View clockView) {
     85         mNotificationIconAreaController = notificationIconAreaController;
     86         mHeadsUpManager = headsUpManager;
     87         mHeadsUpManager.addListener(this);
     88         mHeadsUpStatusBarView = headsUpStatusBarView;
     89         headsUpStatusBarView.setOnDrawingRectChangedListener(
     90                 () -> updateIsolatedIconLocation(true /* requireUpdate */));
     91         mStackScroller = stackScroller;
     92         mPanelView = panelView;
     93         panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
     94         panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
     95         panelView.setHeadsUpAppearanceController(this);
     96         mStackScroller.addOnExpandedHeightListener(mSetExpandedHeight);
     97         mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
     98         mStackScroller.setHeadsUpAppearanceController(this);
     99         mClockView = clockView;
    100         mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
    101         mDarkIconDispatcher.addDarkReceiver(this);
    102     }
    103 
    104 
    105     public void destroy() {
    106         mHeadsUpManager.removeListener(this);
    107         mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
    108         mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
    109         mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
    110         mPanelView.setHeadsUpAppearanceController(null);
    111         mStackScroller.removeOnExpandedHeightListener(mSetExpandedHeight);
    112         mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
    113         mDarkIconDispatcher.removeDarkReceiver(this);
    114     }
    115 
    116     private void updateIsolatedIconLocation(boolean requireStateUpdate) {
    117         mNotificationIconAreaController.setIsolatedIconLocation(
    118                 mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
    119     }
    120 
    121     @Override
    122     public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
    123         updateTopEntry();
    124         updateHeader(headsUp.getEntry());
    125     }
    126 
    127     /** To count the distance from the window right boundary to scroller right boundary. The
    128      * distance formula is the following:
    129      *     Y = screenSize - (SystemWindow's width + Scroller.getRight())
    130      * There are four modes MUST to be considered in Cut Out of RTL.
    131      * No Cut Out:
    132      *   Scroller + NB
    133      *   NB + Scroller
    134      *     => SystemWindow = NavigationBar's width
    135      *     => Y = screenSize - (SystemWindow's width + Scroller.getRight())
    136      * Corner Cut Out or Tall Cut Out:
    137      *   cut out + Scroller + NB
    138      *   NB + Scroller + cut out
    139      *     => SystemWindow = NavigationBar's width
    140      *     => Y = screenSize - (SystemWindow's width + Scroller.getRight())
    141      * Double Cut Out:
    142      *   cut out left + Scroller + (NB + cut out right)
    143      *     SystemWindow = NavigationBar's width + cut out right width
    144      *     => Y = screenSize - (SystemWindow's width + Scroller.getRight())
    145      *   (cut out left + NB) + Scroller + cut out right
    146      *     SystemWindow = NavigationBar's width + cut out left width
    147      *     => Y = screenSize - (SystemWindow's width + Scroller.getRight())
    148      * @return the translation X value for RTL. In theory, it should be negative. i.e. -Y
    149      */
    150     private int getRtlTranslation() {
    151         if (mPoint == null) {
    152             mPoint = new Point();
    153         }
    154 
    155         int realDisplaySize = 0;
    156         if (mStackScroller.getDisplay() != null) {
    157             mStackScroller.getDisplay().getRealSize(mPoint);
    158             realDisplaySize = mPoint.x;
    159         }
    160 
    161         WindowInsets windowInset = mStackScroller.getRootWindowInsets();
    162         return windowInset.getSystemWindowInsetLeft() + mStackScroller.getRight()
    163                 + windowInset.getSystemWindowInsetRight() - realDisplaySize;
    164     }
    165 
    166     public void updatePanelTranslation() {
    167         float newTranslation;
    168         if (mStackScroller.isLayoutRtl()) {
    169             newTranslation = getRtlTranslation();
    170         } else {
    171             newTranslation = mStackScroller.getLeft();
    172         }
    173         newTranslation += mStackScroller.getTranslationX();
    174         mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
    175     }
    176 
    177     private void updateTopEntry() {
    178         NotificationData.Entry newEntry = null;
    179         if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
    180             newEntry = mHeadsUpManager.getTopEntry();
    181         }
    182         NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
    183         mHeadsUpStatusBarView.setEntry(newEntry);
    184         if (newEntry != previousEntry) {
    185             boolean animateIsolation = false;
    186             if (newEntry == null) {
    187                 // no heads up anymore, lets start the disappear animation
    188 
    189                 setShown(false);
    190                 animateIsolation = !mIsExpanded;
    191             } else if (previousEntry == null) {
    192                 // We now have a headsUp and didn't have one before. Let's start the disappear
    193                 // animation
    194                 setShown(true);
    195                 animateIsolation = !mIsExpanded;
    196             }
    197             updateIsolatedIconLocation(false /* requireUpdate */);
    198             mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
    199                     : newEntry.icon, animateIsolation);
    200         }
    201     }
    202 
    203     private void setShown(boolean isShown) {
    204         if (mShown != isShown) {
    205             mShown = isShown;
    206             if (isShown) {
    207                 mHeadsUpStatusBarView.setVisibility(View.VISIBLE);
    208                 CrossFadeHelper.fadeIn(mHeadsUpStatusBarView, CONTENT_FADE_DURATION /* duration */,
    209                         CONTENT_FADE_DELAY /* delay */);
    210                 CrossFadeHelper.fadeOut(mClockView, CONTENT_FADE_DURATION/* duration */,
    211                         0 /* delay */, () -> mClockView.setVisibility(View.INVISIBLE));
    212             } else {
    213                 CrossFadeHelper.fadeIn(mClockView, CONTENT_FADE_DURATION /* duration */,
    214                         CONTENT_FADE_DELAY /* delay */);
    215                 CrossFadeHelper.fadeOut(mHeadsUpStatusBarView, CONTENT_FADE_DURATION/* duration */,
    216                         0 /* delay */, () -> mHeadsUpStatusBarView.setVisibility(View.GONE));
    217 
    218             }
    219         }
    220     }
    221 
    222     @VisibleForTesting
    223     public boolean isShown() {
    224         return mShown;
    225     }
    226 
    227     /**
    228      * Should the headsup status bar view be visible right now? This may be different from isShown,
    229      * since the headsUp manager might not have notified us yet of the state change.
    230      *
    231      * @return if the heads up status bar view should be shown
    232      */
    233     public boolean shouldBeVisible() {
    234         return !mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp();
    235     }
    236 
    237     @Override
    238     public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
    239         updateTopEntry();
    240         updateHeader(headsUp.getEntry());
    241     }
    242 
    243     public void setExpandedHeight(float expandedHeight, float appearFraction) {
    244         boolean changedHeight = expandedHeight != mExpandedHeight;
    245         mExpandedHeight = expandedHeight;
    246         mExpandFraction = appearFraction;
    247         boolean isExpanded = expandedHeight > 0;
    248         if (changedHeight) {
    249             updateHeadsUpHeaders();
    250         }
    251         if (isExpanded != mIsExpanded) {
    252             mIsExpanded = isExpanded;
    253             updateTopEntry();
    254         }
    255     }
    256 
    257     /**
    258      * Set a headsUp to be tracked, meaning that it is currently being pulled down after being
    259      * in a pinned state on the top. The expand animation is different in that case and we need
    260      * to update the header constantly afterwards.
    261      *
    262      * @param trackedChild the tracked headsUp or null if it's not tracking anymore.
    263      */
    264     public void setTrackingHeadsUp(ExpandableNotificationRow trackedChild) {
    265         ExpandableNotificationRow previousTracked = mTrackedChild;
    266         mTrackedChild = trackedChild;
    267         if (previousTracked != null) {
    268             updateHeader(previousTracked.getEntry());
    269         }
    270     }
    271 
    272     private void updateHeadsUpHeaders() {
    273         mHeadsUpManager.getAllEntries().forEach(entry -> {
    274             updateHeader(entry);
    275         });
    276     }
    277 
    278     public void updateHeader(NotificationData.Entry entry) {
    279         ExpandableNotificationRow row = entry.row;
    280         float headerVisibleAmount = 1.0f;
    281         if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) {
    282             headerVisibleAmount = mExpandFraction;
    283         }
    284         row.setHeaderVisibleAmount(headerVisibleAmount);
    285     }
    286 
    287     @Override
    288     public void onDarkChanged(Rect area, float darkIntensity, int tint) {
    289         mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint);
    290     }
    291 
    292     public void setPublicMode(boolean publicMode) {
    293         mHeadsUpStatusBarView.setPublicMode(publicMode);
    294         updateTopEntry();
    295     }
    296 }
    297