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