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