描述
General description:
Music displyed in notification bar after switch New user mode to owner user.
Reproducibility:
10/10
Precondition:
Insert SD card with some song.
Add a New user.
Step:
1.Main Menu->switch to new user->music->play a song->drag down notification bar->click owner switch owner user->drag down notification bar
2.Check the phone display.
Actual result:
Music displyed in notification bar after switch New user mode to owner user.
Expect result:
Music should not displyed in notification bar after switch New user mode to owner user.
发现源头
我们需要考虑到Notification属于SystemUI那一部分,实际是SystemUIApplication.java已经有下列定义。
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.tuner.TunerService.class,
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
Divider.class,
**com.android.systemui.statusbar.SystemBars.class,**
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
com.android.systemui.tv.pip.PipUI.class,
com.android.systemui.shortcut.ShortcutKeyDispatcher.class
};
目前关注于SystemBars这个类,会调用
mStatusBar.start();
PhoneStatusBar.java是BaseStatusBar.java的子类,我们看看它的start方法
@Override
public void start() {
....
super.start();
....
}
很遗憾,又回到了父类BaseStatusBar
public void start() {
....
mNotificationData = new NotificationData(this);
....
createAndAddWindows();
....
}
protected abstract void createAndAddWindows();
嗯,这是个抽象的方法,又回到了子类PhoneStatusBar.java,这是一种代码风格吧!
@Override
public void createAndAddWindows() {
addStatusBarWindow();
}
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = new StatusBarWindowManager(mContext);
mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
mHeadsUpManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
好吧,我编不下去了。我在PhoneStatusbar.java中发现了一段代码
/**
* Updates System UI resources that can be skinned
*/
@Override
public void updateSkinnedResources() {
if (mNavigationBarView != null) {
mNavigationBarView.updateNavigationBarResources();
}
mStatusBarView.updateStatusBarResources();
updateSkinnedResourcesForLockscreen();
refreshBrightnessMirror();
}
这是一段ProgressBar更新的UI模块,想必于我前面找到的SERVICES 数组一定是都有这个方法的,
看看 updateSkinnedResourcesForLockscreen();的实现
/**
* Updates System UI resources that can be skinned for lockscreen
*/
private void updateSkinnedResourcesForLockscreen() {
final Resources res = mContext.getResources();
// Apply skin resources to bouncer if keyguard is showing.
if (mStatusBarKeyguardViewManager != null) {
mStatusBarKeyguardViewManager.resetBouncerView();
}
// Inform lockscreen it needs to update.
SomcLockscreenRuntimeThemeUpdater.newThemeConfiguration(this, (ViewGroup) mStatusBarWindow,
res);
updateNotificationSkinnedResources();
mIconController.updateSkinnedResources();
mIconController.setIconsDarkKeyguard(mState == StatusBarState.KEYGUARD);
((KeyguardStatusView)mKeyguardStatusView).loadClockPluginView(true);
}
根据字面意思的理解,看看updateNotificationSkinnedResources();方法
// all notifications
protected NotificationData mNotificationData;
private void updateNotificationSkinnedResources() {
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
if (activeNotifications != null) {
final int n = activeNotifications.size();
for (int i = 0; i < n; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
entry.row.updateSkinnedResources();
}
}
mKeyguardIconOverflowContainer.updateSkinnedResources();
mStackScroller.updateSkinnedResources();
}
在这里ArrayList类型 activeNotifications表明了用户切换后应该显示的Notification,我们看看这个集合是在哪里获取值的,打开NotificationData.java文件
private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
public ArrayList<Entry> getActiveNotifications() {
return mSortedAndFiltered;
}
// anything changed, and this class should call back the UI so it updates itself.
public void filterAndSort(final boolean filterOutOldNotifications) {
mSortedAndFiltered.clear();
resetHideNotificationState();
synchronized (mEntries) {
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
StatusBarNotification sbn = entry.notification;
if (shouldFilterOut(sbn, filterOutOldNotifications)) {
continue;
}
mSortedAndFiltered.add(entry);
}
}
Collections.sort(mSortedAndFiltered, mRankingComparator);
}
正如注释所描述的那样,这个类会进行UI回调,说明我们前面的流程是正确的,大家可能会好奇,为什么你一下就走对了,其实我也是打了日志,一步步抓log,编译测试,判断下来的。这里面有个if语句,会对我们的Notification进行一个过滤,看看它的具体实现,可能音乐播放的Notification没有被过滤掉。
boolean shouldFilterOut(StatusBarNotification sbn, final boolean filterOutOldNotifications) {
**if (isMediaNotification(sbn)) {
// Exit immediately if this is a media notification, we need to keep it!
return false;
}**
if (!(mEnvironment.isDeviceProvisioned() ||
showNotificationEvenIfUnprovisioned(sbn))) {
return true;
}
if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
return true;
}
if (mEnvironment.onSecureLockScreen() &&
(sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
|| mEnvironment.shouldHideNotifications(sbn.getUserId())
|| mEnvironment.shouldHideNotifications(sbn.getKey()))) {
return true;
}
if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
return true;
}
if (filterOutOldNotifications && (mTimeNotificationsWereLastViewed > sbn.getPostTime())) {
mNotificationsHidden = true;
return true;
}
return false;
}
/**
* Return whether there are any clearable notifications (that aren't errors).
*/
public boolean hasActiveClearableNotifications() {
for (Entry e : mSortedAndFiltered) {
if (e.getContentView() != null) { // the view successfully inflated
if (e.notification.isClearable()) {
return true;
}
}
}
return false;
}
果然,当Notification是媒体类型时直接返回了FALSE,也就是说不过滤掉。不知道是谁改了,只要把这一段代码注释掉就可以了。
/*
if (isMediaNotification(sbn)) {
// Exit immediately if this is a media notification, we need to keep it!
return false;
}
*/
这是最终的方法,意思就是说,媒体类型不做特殊处理,我根据自己的理解,改成这样,也能通过
if (isMediaNotification(sbn)) {
Log.d("elliot_log","ActivityManager.getCurrentUser():"+ActivityManager.getCurrentUser());
Log.d("elliot_log","sbn.getUserId():"+sbn.getUserId());
if(ActivityManager.getCurrentUser()!=sbn.getUserId()){
return true;
}
return false;
}
如果当前用户id不等于Notification所属于的id,则过滤掉。
心得
这个bug让我知道,把原理弄清楚,还是很简单的,没有想象的那么难。