Handle errors inflating notifications (and their icons).
On an inflation error, the StatusBarService cleans up, removes / doesn't add the views, and calls into the StatusBarManagerService, which tells the NotificationManagerService to remove the notification. That then calls all the way back into the StatusBarService, but I think being extra careful is okay. Throughout the status bar, it's all keyed off of the IBinder key, so if the app comes in with a good notification while we're cleaning up, we won't lose the new notification or anything like that. Change-Id: Iea78a637495a8b67810c214b951d5ddb93becacb
This commit is contained in:
@ -36,5 +36,6 @@ interface IStatusBarService
|
|||||||
out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications);
|
out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications);
|
||||||
void visibilityChanged(boolean visible);
|
void visibilityChanged(boolean visible);
|
||||||
void onNotificationClick(String pkg, String tag, int id);
|
void onNotificationClick(String pkg, String tag, int id);
|
||||||
|
void onNotificationError(String pkg, String tag, int id, String message);
|
||||||
void onClearAllNotifications();
|
void onClearAllNotifications();
|
||||||
}
|
}
|
||||||
|
@ -373,6 +373,14 @@ public class PhoneStatusBarService extends StatusBarService {
|
|||||||
oldEntry.content.setOnClickListener(new Launcher(contentIntent,
|
oldEntry.content.setOnClickListener(new Launcher(contentIntent,
|
||||||
notification.pkg, notification.tag, notification.id));
|
notification.pkg, notification.tag, notification.id));
|
||||||
}
|
}
|
||||||
|
// Update the icon.
|
||||||
|
final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
|
||||||
|
notification.notification.icon, notification.notification.iconLevel,
|
||||||
|
notification.notification.number);
|
||||||
|
if (!oldEntry.icon.set(ic)) {
|
||||||
|
handleNotificationError(key, notification, "Couldn't update icon: " + ic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (RuntimeException e) {
|
catch (RuntimeException e) {
|
||||||
// It failed to add cleanly. Log, and remove the view from the panel.
|
// It failed to add cleanly. Log, and remove the view from the panel.
|
||||||
@ -380,9 +388,6 @@ public class PhoneStatusBarService extends StatusBarService {
|
|||||||
removeNotificationViews(key);
|
removeNotificationViews(key);
|
||||||
addNotificationViews(key, notification);
|
addNotificationViews(key, notification);
|
||||||
}
|
}
|
||||||
// Update the icon.
|
|
||||||
oldEntry.icon.set(new StatusBarIcon(notification.pkg, notification.notification.icon,
|
|
||||||
notification.notification.iconLevel, notification.notification.number));
|
|
||||||
} else {
|
} else {
|
||||||
Slog.d(TAG, "not reusing notification");
|
Slog.d(TAG, "not reusing notification");
|
||||||
removeNotificationViews(key);
|
removeNotificationViews(key);
|
||||||
@ -451,8 +456,9 @@ public class PhoneStatusBarService extends StatusBarService {
|
|||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
if (expanded == null) {
|
if (expanded == null) {
|
||||||
Slog.e(TAG, "couldn't inflate view for package " + notification.pkg, exception);
|
String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id);
|
||||||
row.setVisibility(View.GONE);
|
Slog.e(TAG, "couldn't inflate view for notification " + ident);
|
||||||
|
return null;
|
||||||
} else {
|
} else {
|
||||||
content.addView(expanded);
|
content.addView(expanded);
|
||||||
row.setDrawingCacheEnabled(true);
|
row.setDrawingCacheEnabled(true);
|
||||||
@ -474,14 +480,23 @@ public class PhoneStatusBarService extends StatusBarService {
|
|||||||
}
|
}
|
||||||
// Construct the expanded view.
|
// Construct the expanded view.
|
||||||
final View[] views = makeNotificationView(notification, parent);
|
final View[] views = makeNotificationView(notification, parent);
|
||||||
|
if (views == null) {
|
||||||
|
handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
|
||||||
|
+ notification);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final View row = views[0];
|
final View row = views[0];
|
||||||
final View content = views[1];
|
final View content = views[1];
|
||||||
final View expanded = views[2];
|
final View expanded = views[2];
|
||||||
// Construct the icon.
|
// Construct the icon.
|
||||||
StatusBarIconView iconView = new StatusBarIconView(this,
|
final StatusBarIconView iconView = new StatusBarIconView(this,
|
||||||
notification.pkg + "/0x" + Integer.toHexString(notification.id));
|
notification.pkg + "/0x" + Integer.toHexString(notification.id));
|
||||||
iconView.set(new StatusBarIcon(notification.pkg, notification.notification.icon,
|
final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon,
|
||||||
notification.notification.iconLevel, notification.notification.number));
|
notification.notification.iconLevel, notification.notification.number);
|
||||||
|
if (!iconView.set(ic)) {
|
||||||
|
handleNotificationError(key, notification, "Coulding create icon: " + ic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Add the expanded view.
|
// Add the expanded view.
|
||||||
final int viewIndex = list.add(key, notification, row, content, expanded, iconView);
|
final int viewIndex = list.add(key, notification, row, content, expanded, iconView);
|
||||||
parent.addView(row, viewIndex);
|
parent.addView(row, viewIndex);
|
||||||
@ -967,6 +982,21 @@ public class PhoneStatusBarService extends StatusBarService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
|
||||||
|
* about the failure.
|
||||||
|
*
|
||||||
|
* WARNING: this will call back into us. Don't hold any locks.
|
||||||
|
*/
|
||||||
|
void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
|
||||||
|
removeNotification(key);
|
||||||
|
try {
|
||||||
|
mBarService.onNotificationError(n.pkg, n.tag, n.id, message);
|
||||||
|
} catch (RemoteException ex) {
|
||||||
|
// The end is nigh.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class MyTicker extends Ticker {
|
private class MyTicker extends Ticker {
|
||||||
MyTicker(Context context, StatusBarView sb) {
|
MyTicker(Context context, StatusBarView sb) {
|
||||||
super(context, sb);
|
super(context, sb);
|
||||||
|
@ -32,7 +32,6 @@ public class StatusBarIconView extends AnimatedImageView {
|
|||||||
|
|
||||||
private StatusBarIcon mIcon;
|
private StatusBarIcon mIcon;
|
||||||
@ViewDebug.ExportedProperty private String mSlot;
|
@ViewDebug.ExportedProperty private String mSlot;
|
||||||
@ViewDebug.ExportedProperty private boolean mError;
|
|
||||||
|
|
||||||
public StatusBarIconView(Context context, String slot) {
|
public StatusBarIconView(Context context, String slot) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -52,39 +51,33 @@ public class StatusBarIconView extends AnimatedImageView {
|
|||||||
return a.equals(b);
|
return a.equals(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(StatusBarIcon icon) {
|
/**
|
||||||
error: {
|
* Returns whether the set succeeded.
|
||||||
final boolean iconEquals = !mError
|
*/
|
||||||
&& mIcon != null
|
public boolean set(StatusBarIcon icon) {
|
||||||
&& streq(mIcon.iconPackage, icon.iconPackage)
|
final boolean iconEquals = mIcon != null
|
||||||
&& mIcon.iconId == icon.iconId;
|
&& streq(mIcon.iconPackage, icon.iconPackage)
|
||||||
final boolean levelEquals = !mError
|
&& mIcon.iconId == icon.iconId;
|
||||||
&& iconEquals
|
final boolean levelEquals = iconEquals
|
||||||
&& mIcon.iconLevel == icon.iconLevel;
|
&& mIcon.iconLevel == icon.iconLevel;
|
||||||
final boolean visibilityEquals = !mError
|
final boolean visibilityEquals = mIcon != null
|
||||||
&& mIcon != null
|
&& mIcon.visible == icon.visible;
|
||||||
&& mIcon.visible == icon.visible;
|
if (!iconEquals) {
|
||||||
mError = false;
|
Drawable drawable = getIcon(icon);
|
||||||
if (!iconEquals) {
|
if (drawable == null) {
|
||||||
Drawable drawable = getIcon(icon);
|
Slog.w(PhoneStatusBarService.TAG, "No icon for slot " + mSlot);
|
||||||
if (drawable == null) {
|
return false;
|
||||||
mError = true;
|
|
||||||
Slog.w(PhoneStatusBarService.TAG, "No icon ID for slot " + mSlot);
|
|
||||||
break error;
|
|
||||||
}
|
|
||||||
setImageDrawable(drawable);
|
|
||||||
}
|
}
|
||||||
if (!levelEquals) {
|
setImageDrawable(drawable);
|
||||||
setImageLevel(icon.iconLevel);
|
|
||||||
}
|
|
||||||
if (!visibilityEquals) {
|
|
||||||
setVisibility(icon.visible ? VISIBLE : GONE);
|
|
||||||
}
|
|
||||||
mIcon = icon.clone();
|
|
||||||
}
|
}
|
||||||
if (mError) {
|
if (!levelEquals) {
|
||||||
setVisibility(GONE);
|
setImageLevel(icon.iconLevel);
|
||||||
}
|
}
|
||||||
|
if (!visibilityEquals) {
|
||||||
|
setVisibility(icon.visible ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
mIcon = icon.clone();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getIcon(StatusBarIcon icon) {
|
private Drawable getIcon(StatusBarIcon icon) {
|
||||||
@ -106,7 +99,7 @@ public class StatusBarIconView extends AnimatedImageView {
|
|||||||
try {
|
try {
|
||||||
r = context.getPackageManager().getResourcesForApplication(icon.iconPackage);
|
r = context.getPackageManager().getResourcesForApplication(icon.iconPackage);
|
||||||
} catch (PackageManager.NameNotFoundException ex) {
|
} catch (PackageManager.NameNotFoundException ex) {
|
||||||
Slog.e(PhoneStatusBarService.TAG, "Icon package not found: "+icon.iconPackage, ex);
|
Slog.e(PhoneStatusBarService.TAG, "Icon package not found: " + icon.iconPackage);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -303,6 +303,12 @@ class NotificationManagerService extends INotificationManager.Stub
|
|||||||
updateLightsLocked();
|
updateLightsLocked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onNotificationError(String pkg, String tag, int id, String message) {
|
||||||
|
Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id);
|
||||||
|
cancelNotification(pkg, tag, id, 0, 0);
|
||||||
|
// TODO: Tell the activity manager.
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||||
|
@ -87,6 +87,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
|
|||||||
void onClearAll();
|
void onClearAll();
|
||||||
void onNotificationClick(String pkg, String tag, int id);
|
void onNotificationClick(String pkg, String tag, int id);
|
||||||
void onPanelRevealed();
|
void onPanelRevealed();
|
||||||
|
void onNotificationError(String pkg, String tag, int id, String message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -279,6 +280,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub
|
|||||||
mNotificationCallbacks.onNotificationClick(pkg, tag, id);
|
mNotificationCallbacks.onNotificationClick(pkg, tag, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onNotificationError(String pkg, String tag, int id, String message) {
|
||||||
|
Slog.d(TAG, "onNotificationError message=" + message);
|
||||||
|
// WARNING: this will call back into us to do the remove. Don't hold any locks.
|
||||||
|
mNotificationCallbacks.onNotificationError(pkg, tag, id, message);
|
||||||
|
}
|
||||||
|
|
||||||
public void onClearAllNotifications() {
|
public void onClearAllNotifications() {
|
||||||
mNotificationCallbacks.onClearAll();
|
mNotificationCallbacks.onClearAll();
|
||||||
}
|
}
|
||||||
|
@ -123,29 +123,49 @@ public class NotificationTestList extends TestActivity
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new Test("Bad Icon") {
|
new Test("Bad Icon #1 (when=create)") {
|
||||||
public void run() {
|
public void run() {
|
||||||
mNM.notify(1, new Notification(NotificationTestList.this,
|
Notification n = new Notification(R.layout.chrono_notification /* not an icon */,
|
||||||
R.layout.chrono_notification, /* not a drawable! */
|
null, mActivityCreateTime);
|
||||||
null, System.currentTimeMillis()-(1000*60*60*24),
|
n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
|
||||||
"(453) 123-2328",
|
"This is the same notification!!!", makeIntent());
|
||||||
"", null));
|
mNM.notify(1, n);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new Test("Bad resource #2") {
|
new Test("Bad Icon #1 (when=now)") {
|
||||||
public void run()
|
public void run() {
|
||||||
{
|
Notification n = new Notification(R.layout.chrono_notification /* not an icon */,
|
||||||
Notification n = new Notification(NotificationTestList.this,
|
null, System.currentTimeMillis());
|
||||||
R.drawable.ic_statusbar_missedcall,
|
n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
|
||||||
null, System.currentTimeMillis()-(1000*60*60*24),
|
"This is the same notification!!!", makeIntent());
|
||||||
"(453) 123-2328",
|
mNM.notify(1, n);
|
||||||
"", null);
|
|
||||||
n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
|
|
||||||
mNM.notify(2, n);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
new Test("Bad resource #1 (when=create)") {
|
||||||
|
public void run() {
|
||||||
|
Notification n = new Notification(R.drawable.icon2,
|
||||||
|
null, mActivityCreateTime);
|
||||||
|
n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
|
||||||
|
"This is the same notification!!!", makeIntent());
|
||||||
|
n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
|
||||||
|
mNM.notify(1, n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new Test("Bad resource #1 (when=now)") {
|
||||||
|
public void run() {
|
||||||
|
Notification n = new Notification(R.drawable.icon2,
|
||||||
|
null, System.currentTimeMillis());
|
||||||
|
n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
|
||||||
|
"This is the same notification!!!", makeIntent());
|
||||||
|
n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
|
||||||
|
mNM.notify(1, n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
new Test("Bad resource #3") {
|
new Test("Bad resource #3") {
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user