Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2008 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;
     18 
     19 import android.app.Notification;
     20 import android.content.Context;
     21 import android.content.pm.ApplicationInfo;
     22 import android.content.res.Configuration;
     23 import android.content.res.Resources;
     24 import android.graphics.Canvas;
     25 import android.graphics.Paint;
     26 import android.graphics.Rect;
     27 import android.graphics.drawable.Drawable;
     28 import android.graphics.drawable.Icon;
     29 import android.os.Parcelable;
     30 import android.os.UserHandle;
     31 import android.text.TextUtils;
     32 import android.util.AttributeSet;
     33 import android.util.Log;
     34 import android.util.TypedValue;
     35 import android.view.ViewDebug;
     36 import android.view.accessibility.AccessibilityEvent;
     37 
     38 import com.android.internal.statusbar.StatusBarIcon;
     39 import com.android.systemui.R;
     40 
     41 import java.text.NumberFormat;
     42 
     43 public class StatusBarIconView extends AnimatedImageView {
     44     private static final String TAG = "StatusBarIconView";
     45     private boolean mAlwaysScaleIcon;
     46 
     47     private StatusBarIcon mIcon;
     48     @ViewDebug.ExportedProperty private String mSlot;
     49     private Drawable mNumberBackground;
     50     private Paint mNumberPain;
     51     private int mNumberX;
     52     private int mNumberY;
     53     private String mNumberText;
     54     private Notification mNotification;
     55     private final boolean mBlocked;
     56     private int mDensity;
     57 
     58     public StatusBarIconView(Context context, String slot, Notification notification) {
     59         this(context, slot, notification, false);
     60     }
     61 
     62     public StatusBarIconView(Context context, String slot, Notification notification,
     63             boolean blocked) {
     64         super(context);
     65         mBlocked = blocked;
     66         mSlot = slot;
     67         mNumberPain = new Paint();
     68         mNumberPain.setTextAlign(Paint.Align.CENTER);
     69         mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
     70         mNumberPain.setAntiAlias(true);
     71         setNotification(notification);
     72         maybeUpdateIconScale();
     73         setScaleType(ScaleType.CENTER);
     74         mDensity = context.getResources().getDisplayMetrics().densityDpi;
     75     }
     76 
     77     private void maybeUpdateIconScale() {
     78         // We do not resize and scale system icons (on the right), only notification icons (on the
     79         // left).
     80         if (mNotification != null || mAlwaysScaleIcon) {
     81             updateIconScale();
     82         }
     83     }
     84 
     85     private void updateIconScale() {
     86         Resources res = mContext.getResources();
     87         final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
     88         final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
     89         final float scale = (float)imageBounds / (float)outerBounds;
     90         setScaleX(scale);
     91         setScaleY(scale);
     92     }
     93 
     94     @Override
     95     protected void onConfigurationChanged(Configuration newConfig) {
     96         super.onConfigurationChanged(newConfig);
     97         int density = newConfig.densityDpi;
     98         if (density != mDensity) {
     99             mDensity = density;
    100             maybeUpdateIconScale();
    101             updateDrawable();
    102         }
    103     }
    104 
    105     public void setNotification(Notification notification) {
    106         mNotification = notification;
    107         setContentDescription(notification);
    108     }
    109 
    110     public StatusBarIconView(Context context, AttributeSet attrs) {
    111         super(context, attrs);
    112         mBlocked = false;
    113         mAlwaysScaleIcon = true;
    114         updateIconScale();
    115         mDensity = context.getResources().getDisplayMetrics().densityDpi;
    116     }
    117 
    118     private static boolean streq(String a, String b) {
    119         if (a == b) {
    120             return true;
    121         }
    122         if (a == null && b != null) {
    123             return false;
    124         }
    125         if (a != null && b == null) {
    126             return false;
    127         }
    128         return a.equals(b);
    129     }
    130 
    131     public boolean equalIcons(Icon a, Icon b) {
    132         if (a == b) return true;
    133         if (a.getType() != b.getType()) return false;
    134         switch (a.getType()) {
    135             case Icon.TYPE_RESOURCE:
    136                 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
    137             case Icon.TYPE_URI:
    138                 return a.getUriString().equals(b.getUriString());
    139             default:
    140                 return false;
    141         }
    142     }
    143     /**
    144      * Returns whether the set succeeded.
    145      */
    146     public boolean set(StatusBarIcon icon) {
    147         final boolean iconEquals = mIcon != null && equalIcons(mIcon.icon, icon.icon);
    148         final boolean levelEquals = iconEquals
    149                 && mIcon.iconLevel == icon.iconLevel;
    150         final boolean visibilityEquals = mIcon != null
    151                 && mIcon.visible == icon.visible;
    152         final boolean numberEquals = mIcon != null
    153                 && mIcon.number == icon.number;
    154         mIcon = icon.clone();
    155         setContentDescription(icon.contentDescription);
    156         if (!iconEquals) {
    157             if (!updateDrawable(false /* no clear */)) return false;
    158         }
    159         if (!levelEquals) {
    160             setImageLevel(icon.iconLevel);
    161         }
    162 
    163         if (!numberEquals) {
    164             if (icon.number > 0 && getContext().getResources().getBoolean(
    165                         R.bool.config_statusBarShowNumber)) {
    166                 if (mNumberBackground == null) {
    167                     mNumberBackground = getContext().getResources().getDrawable(
    168                             R.drawable.ic_notification_overlay);
    169                 }
    170                 placeNumber();
    171             } else {
    172                 mNumberBackground = null;
    173                 mNumberText = null;
    174             }
    175             invalidate();
    176         }
    177         if (!visibilityEquals) {
    178             setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
    179         }
    180         return true;
    181     }
    182 
    183     public void updateDrawable() {
    184         updateDrawable(true /* with clear */);
    185     }
    186 
    187     private boolean updateDrawable(boolean withClear) {
    188         if (mIcon == null) {
    189             return false;
    190         }
    191         Drawable drawable = getIcon(mIcon);
    192         if (drawable == null) {
    193             Log.w(TAG, "No icon for slot " + mSlot);
    194             return false;
    195         }
    196         if (withClear) {
    197             setImageDrawable(null);
    198         }
    199         setImageDrawable(drawable);
    200         return true;
    201     }
    202 
    203     private Drawable getIcon(StatusBarIcon icon) {
    204         return getIcon(getContext(), icon);
    205     }
    206 
    207     /**
    208      * Returns the right icon to use for this item
    209      *
    210      * @param context Context to use to get resources
    211      * @return Drawable for this item, or null if the package or item could not
    212      *         be found
    213      */
    214     public static Drawable getIcon(Context context, StatusBarIcon statusBarIcon) {
    215         int userId = statusBarIcon.user.getIdentifier();
    216         if (userId == UserHandle.USER_ALL) {
    217             userId = UserHandle.USER_SYSTEM;
    218         }
    219 
    220         Drawable icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
    221 
    222         TypedValue typedValue = new TypedValue();
    223         context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
    224         float scaleFactor = typedValue.getFloat();
    225 
    226         // No need to scale the icon, so return it as is.
    227         if (scaleFactor == 1.f) {
    228             return icon;
    229         }
    230 
    231         return new ScalingDrawableWrapper(icon, scaleFactor);
    232     }
    233 
    234     public StatusBarIcon getStatusBarIcon() {
    235         return mIcon;
    236     }
    237 
    238     @Override
    239     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    240         super.onInitializeAccessibilityEvent(event);
    241         if (mNotification != null) {
    242             event.setParcelableData(mNotification);
    243         }
    244     }
    245 
    246     @Override
    247     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    248         super.onSizeChanged(w, h, oldw, oldh);
    249         if (mNumberBackground != null) {
    250             placeNumber();
    251         }
    252     }
    253 
    254     @Override
    255     public void onRtlPropertiesChanged(int layoutDirection) {
    256         super.onRtlPropertiesChanged(layoutDirection);
    257         updateDrawable();
    258     }
    259 
    260     @Override
    261     protected void onDraw(Canvas canvas) {
    262         super.onDraw(canvas);
    263 
    264         if (mNumberBackground != null) {
    265             mNumberBackground.draw(canvas);
    266             canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
    267         }
    268     }
    269 
    270     @Override
    271     protected void debug(int depth) {
    272         super.debug(depth);
    273         Log.d("View", debugIndent(depth) + "slot=" + mSlot);
    274         Log.d("View", debugIndent(depth) + "icon=" + mIcon);
    275     }
    276 
    277     void placeNumber() {
    278         final String str;
    279         final int tooBig = getContext().getResources().getInteger(
    280                 android.R.integer.status_bar_notification_info_maxnum);
    281         if (mIcon.number > tooBig) {
    282             str = getContext().getResources().getString(
    283                         android.R.string.status_bar_notification_info_overflow);
    284         } else {
    285             NumberFormat f = NumberFormat.getIntegerInstance();
    286             str = f.format(mIcon.number);
    287         }
    288         mNumberText = str;
    289 
    290         final int w = getWidth();
    291         final int h = getHeight();
    292         final Rect r = new Rect();
    293         mNumberPain.getTextBounds(str, 0, str.length(), r);
    294         final int tw = r.right - r.left;
    295         final int th = r.bottom - r.top;
    296         mNumberBackground.getPadding(r);
    297         int dw = r.left + tw + r.right;
    298         if (dw < mNumberBackground.getMinimumWidth()) {
    299             dw = mNumberBackground.getMinimumWidth();
    300         }
    301         mNumberX = w-r.right-((dw-r.right-r.left)/2);
    302         int dh = r.top + th + r.bottom;
    303         if (dh < mNumberBackground.getMinimumWidth()) {
    304             dh = mNumberBackground.getMinimumWidth();
    305         }
    306         mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
    307         mNumberBackground.setBounds(w-dw, h-dh, w, h);
    308     }
    309 
    310     private void setContentDescription(Notification notification) {
    311         if (notification != null) {
    312             String d = contentDescForNotification(mContext, notification);
    313             if (!TextUtils.isEmpty(d)) {
    314                 setContentDescription(d);
    315             }
    316         }
    317     }
    318 
    319     public String toString() {
    320         return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
    321             + " notification=" + mNotification + ")";
    322     }
    323 
    324     public String getSlot() {
    325         return mSlot;
    326     }
    327 
    328 
    329     public static String contentDescForNotification(Context c, Notification n) {
    330         String appName = "";
    331         try {
    332             Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
    333             appName = builder.loadHeaderAppName();
    334         } catch (RuntimeException e) {
    335             Log.e(TAG, "Unable to recover builder", e);
    336             // Trying to get the app name from the app info instead.
    337             Parcelable appInfo = n.extras.getParcelable(
    338                     Notification.EXTRA_BUILDER_APPLICATION_INFO);
    339             if (appInfo instanceof ApplicationInfo) {
    340                 appName = String.valueOf(((ApplicationInfo) appInfo).loadLabel(
    341                         c.getPackageManager()));
    342             }
    343         }
    344 
    345         CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
    346         CharSequence ticker = n.tickerText;
    347 
    348         CharSequence desc = !TextUtils.isEmpty(ticker) ? ticker
    349                 : !TextUtils.isEmpty(title) ? title : "";
    350 
    351         return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
    352     }
    353 
    354 }
    355