Home | History | Annotate | Download | only in phone
      1 package com.android.systemui.statusbar.phone;
      2 
      3 import android.content.Context;
      4 import android.content.res.Resources;
      5 import android.graphics.Color;
      6 import android.graphics.Rect;
      7 import android.support.annotation.NonNull;
      8 import android.support.v4.util.ArrayMap;
      9 import android.view.LayoutInflater;
     10 import android.view.View;
     11 import android.widget.FrameLayout;
     12 
     13 import com.android.internal.statusbar.StatusBarIcon;
     14 import com.android.internal.util.NotificationColorUtil;
     15 import com.android.systemui.Dependency;
     16 import com.android.systemui.R;
     17 import com.android.systemui.statusbar.ExpandableNotificationRow;
     18 import com.android.systemui.statusbar.NotificationData;
     19 import com.android.systemui.statusbar.NotificationEntryManager;
     20 import com.android.systemui.statusbar.NotificationShelf;
     21 import com.android.systemui.statusbar.StatusBarIconView;
     22 import com.android.systemui.statusbar.notification.NotificationUtils;
     23 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
     24 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
     25 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     26 
     27 import java.util.ArrayList;
     28 import java.util.function.Function;
     29 
     30 /**
     31  * A controller for the space in the status bar to the left of the system icons. This area is
     32  * normally reserved for notifications.
     33  */
     34 public class NotificationIconAreaController implements DarkReceiver {
     35     private final NotificationColorUtil mNotificationColorUtil;
     36     private final NotificationEntryManager mEntryManager;
     37     private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
     38 
     39     private int mIconSize;
     40     private int mIconHPadding;
     41     private int mIconTint = Color.WHITE;
     42 
     43     private StatusBar mStatusBar;
     44     protected View mNotificationIconArea;
     45     private NotificationIconContainer mNotificationIcons;
     46     private NotificationIconContainer mShelfIcons;
     47     private final Rect mTintArea = new Rect();
     48     private NotificationStackScrollLayout mNotificationScrollLayout;
     49     private Context mContext;
     50 
     51     public NotificationIconAreaController(Context context, StatusBar statusBar) {
     52         mStatusBar = statusBar;
     53         mNotificationColorUtil = NotificationColorUtil.getInstance(context);
     54         mContext = context;
     55         mEntryManager = Dependency.get(NotificationEntryManager.class);
     56 
     57         initializeNotificationAreaViews(context);
     58     }
     59 
     60     protected View inflateIconArea(LayoutInflater inflater) {
     61         return inflater.inflate(R.layout.notification_icon_area, null);
     62     }
     63 
     64     /**
     65      * Initializes the views that will represent the notification area.
     66      */
     67     protected void initializeNotificationAreaViews(Context context) {
     68         reloadDimens(context);
     69 
     70         LayoutInflater layoutInflater = LayoutInflater.from(context);
     71         mNotificationIconArea = inflateIconArea(layoutInflater);
     72         mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
     73                 R.id.notificationIcons);
     74 
     75         mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
     76     }
     77 
     78     public void setupShelf(NotificationShelf shelf) {
     79         mShelfIcons = shelf.getShelfIcons();
     80         shelf.setCollapsedIcons(mNotificationIcons);
     81     }
     82 
     83     public void onDensityOrFontScaleChanged(Context context) {
     84         reloadDimens(context);
     85         final FrameLayout.LayoutParams params = generateIconLayoutParams();
     86         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
     87             View child = mNotificationIcons.getChildAt(i);
     88             child.setLayoutParams(params);
     89         }
     90         for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
     91             View child = mShelfIcons.getChildAt(i);
     92             child.setLayoutParams(params);
     93         }
     94     }
     95 
     96     @NonNull
     97     private FrameLayout.LayoutParams generateIconLayoutParams() {
     98         return new FrameLayout.LayoutParams(
     99                 mIconSize + 2 * mIconHPadding, getHeight());
    100     }
    101 
    102     private void reloadDimens(Context context) {
    103         Resources res = context.getResources();
    104         mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
    105         mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
    106     }
    107 
    108     /**
    109      * Returns the view that represents the notification area.
    110      */
    111     public View getNotificationInnerAreaView() {
    112         return mNotificationIconArea;
    113     }
    114 
    115     /**
    116      * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
    117      * Sets the color that should be used to tint any icons in the notification area.
    118      *
    119      * @param tintArea the area in which to tint the icons, specified in screen coordinates
    120      * @param darkIntensity
    121      */
    122     public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
    123         if (tintArea == null) {
    124             mTintArea.setEmpty();
    125         } else {
    126             mTintArea.set(tintArea);
    127         }
    128         if (mNotificationIconArea != null) {
    129             if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
    130                 mIconTint = iconTint;
    131             }
    132         } else {
    133             mIconTint = iconTint;
    134         }
    135 
    136         applyNotificationIconsTint();
    137     }
    138 
    139     protected int getHeight() {
    140         return mStatusBar.getStatusBarHeight();
    141     }
    142 
    143     protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
    144             boolean showAmbient, boolean hideDismissed, boolean hideRepliedMessages) {
    145         if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
    146             return false;
    147         }
    148         if (!StatusBar.isTopLevelChild(entry)) {
    149             return false;
    150         }
    151         if (entry.row.getVisibility() == View.GONE) {
    152             return false;
    153         }
    154         if (entry.row.isDismissed() && hideDismissed) {
    155             return false;
    156         }
    157 
    158         if (hideRepliedMessages && entry.isLastMessageFromReply()) {
    159             return false;
    160         }
    161 
    162         // showAmbient == show in shade but not shelf
    163         if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar(entry)) {
    164             return false;
    165         }
    166 
    167         return true;
    168     }
    169 
    170     /**
    171      * Updates the notifications with the given list of notifications to display.
    172      */
    173     public void updateNotificationIcons() {
    174 
    175         updateStatusBarIcons();
    176         updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
    177                 NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */,
    178                 false /* hideRepliedMessages */);
    179 
    180         applyNotificationIconsTint();
    181     }
    182 
    183     public void updateStatusBarIcons() {
    184         updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
    185                 false /* showAmbient */, true /* hideDismissed */, true /* hideRepliedMessages */);
    186     }
    187 
    188     /**
    189      * Updates the notification icons for a host layout. This will ensure that the notification
    190      * host layout will have the same icons like the ones in here.
    191      * @param function A function to look up an icon view based on an entry
    192      * @param hostLayout which layout should be updated
    193      * @param showAmbient should ambient notification icons be shown
    194      * @param hideDismissed should dismissed icons be hidden
    195      * @param hideRepliedMessages should messages that have been replied to be hidden
    196      */
    197     private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
    198             NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed,
    199             boolean hideRepliedMessages) {
    200         ArrayList<StatusBarIconView> toShow = new ArrayList<>(
    201                 mNotificationScrollLayout.getChildCount());
    202 
    203         // Filter out ambient notifications and notification children.
    204         for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
    205             View view = mNotificationScrollLayout.getChildAt(i);
    206             if (view instanceof ExpandableNotificationRow) {
    207                 NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
    208                 if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed,
    209                         hideRepliedMessages)) {
    210                     toShow.add(function.apply(ent));
    211                 }
    212             }
    213         }
    214 
    215         // In case we are changing the suppression of a group, the replacement shouldn't flicker
    216         // and it should just be replaced instead. We therefore look for notifications that were
    217         // just replaced by the child or vice-versa to suppress this.
    218 
    219         ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
    220         ArrayList<View> toRemove = new ArrayList<>();
    221         for (int i = 0; i < hostLayout.getChildCount(); i++) {
    222             View child = hostLayout.getChildAt(i);
    223             if (!(child instanceof StatusBarIconView)) {
    224                 continue;
    225             }
    226             if (!toShow.contains(child)) {
    227                 boolean iconWasReplaced = false;
    228                 StatusBarIconView removedIcon = (StatusBarIconView) child;
    229                 String removedGroupKey = removedIcon.getNotification().getGroupKey();
    230                 for (int j = 0; j < toShow.size(); j++) {
    231                     StatusBarIconView candidate = toShow.get(j);
    232                     if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
    233                             && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
    234                         if (!iconWasReplaced) {
    235                             iconWasReplaced = true;
    236                         } else {
    237                             iconWasReplaced = false;
    238                             break;
    239                         }
    240                     }
    241                 }
    242                 if (iconWasReplaced) {
    243                     ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
    244                     if (statusBarIcons == null) {
    245                         statusBarIcons = new ArrayList<>();
    246                         replacingIcons.put(removedGroupKey, statusBarIcons);
    247                     }
    248                     statusBarIcons.add(removedIcon.getStatusBarIcon());
    249                 }
    250                 toRemove.add(removedIcon);
    251             }
    252         }
    253         // removing all duplicates
    254         ArrayList<String> duplicates = new ArrayList<>();
    255         for (String key : replacingIcons.keySet()) {
    256             ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
    257             if (statusBarIcons.size() != 1) {
    258                 duplicates.add(key);
    259             }
    260         }
    261         replacingIcons.removeAll(duplicates);
    262         hostLayout.setReplacingIcons(replacingIcons);
    263 
    264         final int toRemoveCount = toRemove.size();
    265         for (int i = 0; i < toRemoveCount; i++) {
    266             hostLayout.removeView(toRemove.get(i));
    267         }
    268 
    269         final FrameLayout.LayoutParams params = generateIconLayoutParams();
    270         for (int i = 0; i < toShow.size(); i++) {
    271             StatusBarIconView v = toShow.get(i);
    272             // The view might still be transiently added if it was just removed and added again
    273             hostLayout.removeTransientView(v);
    274             if (v.getParent() == null) {
    275                 if (hideDismissed) {
    276                     v.setOnDismissListener(mUpdateStatusBarIcons);
    277                 }
    278                 hostLayout.addView(v, i, params);
    279             }
    280         }
    281 
    282         hostLayout.setChangingViewPositions(true);
    283         // Re-sort notification icons
    284         final int childCount = hostLayout.getChildCount();
    285         for (int i = 0; i < childCount; i++) {
    286             View actual = hostLayout.getChildAt(i);
    287             StatusBarIconView expected = toShow.get(i);
    288             if (actual == expected) {
    289                 continue;
    290             }
    291             hostLayout.removeView(expected);
    292             hostLayout.addView(expected, i);
    293         }
    294         hostLayout.setChangingViewPositions(false);
    295         hostLayout.setReplacingIcons(null);
    296     }
    297 
    298     /**
    299      * Applies {@link #mIconTint} to the notification icons.
    300      */
    301     private void applyNotificationIconsTint() {
    302         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
    303             final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
    304             if (iv.getWidth() != 0) {
    305                 updateTintForIcon(iv);
    306             } else {
    307                 iv.executeOnLayout(() -> updateTintForIcon(iv));
    308             }
    309         }
    310     }
    311 
    312     private void updateTintForIcon(StatusBarIconView v) {
    313         boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
    314         int color = StatusBarIconView.NO_COLOR;
    315         boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
    316         if (colorize) {
    317             color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
    318         }
    319         v.setStaticDrawableColor(color);
    320         v.setDecorColor(mIconTint);
    321     }
    322 
    323     public void setDark(boolean dark) {
    324         mNotificationIcons.setDark(dark, false, 0);
    325         mShelfIcons.setDark(dark, false, 0);
    326     }
    327 
    328     public void showIconIsolated(StatusBarIconView icon, boolean animated) {
    329         mNotificationIcons.showIconIsolated(icon, animated);
    330     }
    331 
    332     public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
    333         mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
    334     }
    335 }
    336