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