您的位置:首页 > 移动开发 > Android开发

Android U盘拔插提示音分析

2015-11-19 16:34 936 查看

U盘提示音源头追溯

对Android系统的架构,业务流程比较熟悉的老手来说就可以直接经验定位,迅速找到源码。
新手一般通过捕捉到字串,图片来找对应功能的源码略显笨拙,也是菜鸟期的必经之路。
其实U盘提示音也属于Android 系统的状态栏,通知管理类展示的功能之一,那么就从该服务(notification)开始查找。
dumpsys notification
前面的博客中有使用到dumpsys,此处我们也用它来查询服务notification,在拔插完u盘之后,执行以上命令,会得到如下信息:



从图中可以得到不少关键信息,mDisabledNotifications,mSoundNotification,mVibrateNotification,com.android.systemui,tickerText=USB存储设备插上
这个关键信息中成员变量mDisabledNotifications,mSoundNotification,mVibrateNotification在系统中,可以搜索到属于NotificationManagerService.java,
而在framerowk中搜索可以发现frameworks/base/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java文件中有对tickerText的引用,刚好也
属于com.android.systemui模块。

Usb设备监听

从上面已经找到了处理U盘拔插的处理逻辑在文件StorageNotification.java中,首先我们就看拔插事件的监听器
private class StorageNotificationEventListener extends StorageEventListener {
public void onUsbMassStorageConnectionChanged(final boolean connected) {
mAsyncEventHandler.post(new Runnable() {
@Override
public void run() {
onUsbMassStorageConnectionChangedAsync(connected);
}
});
}
public void onStorageStateChanged(final String path,
final String oldState, final String newState) {
mAsyncEventHandler.post(new Runnable() {
@Override
public void run() {
onStorageStateChangedAsync(path, oldState, newState);
}
});
}
}
StorageNotification在start时,将StorageNotificationEventListener类型监听器注册进外部设备管理类StorageManager中
public void start() {
mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
final boolean connected = mStorageManager.isUsbMassStorageConnected();
if (DEBUG) Log.d(TAG, String.format( "Startup with UMS connection %s (media state %s)",
mUmsAvailable, Environment.getExternalStorageState()));

HandlerThread thr = new HandlerThread("SystemUI StorageNotification");
thr.start();
mAsyncEventHandler = new Handler(thr.getLooper());

StorageNotificationEventListener listener = new StorageNotificationEventListener();
listener.onUsbMassStorageConnectionChanged(connected);
mStorageManager.registerListener(listener);
}
当有U盘拔插事件时,onStorageStateChanged函数会被调用,接着onStorageStateChangedAsync,最后setUsbStorageNotification会被触发。
/**
* Sets the USB storage notification.
*/
private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,
boolean sound, boolean visible, PendingIntent pi) {

if (!visible && mUsbStorageNotification == null) {
return;
}

NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);

if (notificationManager == null) {
return;
}

if (visible) {
Resources r = Resources.getSystem();
CharSequence title = r.getText(titleId);
CharSequence message = r.getText(messageId);

if (mUsbStorageNotification == null) {
mUsbStorageNotification = new Notification();
mUsbStorageNotification.icon = icon;
mUsbStorageNotification.when = 0;
}

if (sound) {
mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
} else {
mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
}

mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;

mUsbStorageNotification.tickerText = title;
if (pi == null) {
Intent intent = new Intent();
pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
UserHandle.CURRENT);
}

mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
final boolean adbOn = 1 == Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.ADB_ENABLED,
0);

if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {
// Pop up a full-screen alert to coach the user through enabling UMS. The average
// user has attached the device to USB either to charge the phone (in which case
// this is harmless) or transfer files, and in the latter case this alert saves
// several steps (as well as subtly indicates that you shouldn't mix UMS with other
// activities on the device).
//
// If ADB is enabled, however, we suppress this dialog (under the assumption that a
// developer (a) knows how to enable UMS, and (b) is probably using USB to install
// builds or use adb commands.
mUsbStorageNotification.fullScreenIntent = pi;
}
}

final int notificationId = mUsbStorageNotification.icon;
if (visible) {
notificationManager.notifyAsUser(null, notificationId, mUsbStorageNotification,
UserHandle.ALL);
} else {
notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
}
}
该函数主要是封装一个notification,也即mUsbStorageNotification,并通过PendingIntent意图来处理,包括标题,声音,消息,按钮等,最后通过NotificationManager服务通过系统各用户(notifyAsUser)。

NotificationManager接管notification

notifyAsUser处理如下:
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
notification, idOut, user.getIdentifier());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
}
}
获取Notification.sound的 Uri值,接着进入NotificationManagerService.enqueueNotificationWithTag,再接着enqueueNotificationInternal
// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
// uid/pid of another application)

public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId)
{
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
}
checkCallerIsSystemOrSameApp(pkg);
final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));

final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
final UserHandle user = new UserHandle(userId);

// Limit the number of notifications that any given package except the android
// package can enqueue.  Prevents DOS attacks and deals with leaks.
if (!isSystemNotification) {
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final NotificationRecord r = mNotificationList.get(i);
if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " notifications.  Not showing more.  package=" + pkg);
return;
}
}
}
}
}

// This conditional is a dirty hack to limit the logging done on
//     behalf of the download manager without affecting other apps.
if (!pkg.equals("com.android.providers.downloads")
|| Log.isLoggable("DownloadManager", Log.VERBOSE)) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId,
notification.toString());
}

if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
if (notification.icon != 0) {
if (notification.contentView == null) {
throw new IllegalArgumentException("contentView required: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
}

mHandler.post(new Runnable() {
@Override
public void run() {

// === Scoring ===

// 0. Sanitize inputs
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
Notification.PRIORITY_MAX);
// Migrate notification flags to scores
if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) {
if (notification.priority < Notification.PRIORITY_MAX) {
notification.priority = Notification.PRIORITY_MAX;
}
} else if (SCORE_ONGOING_HIGHER &&
0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
if (notification.priority < Notification.PRIORITY_HIGH) {
notification.priority = Notification.PRIORITY_HIGH;
}
}

// 1. initial score: buckets of 10, around the app
int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]

// 2. Consult external heuristics (TBD)

// 3. Apply local rules

int initialScore = score;
if (!mScorers.isEmpty()) {
if (DBG) Slog.v(TAG, "Initial score is " + score + ".");
for (NotificationScorer scorer : mScorers) {
try {
score = scorer.getScore(notification, score);
} catch (Throwable t) {
Slog.w(TAG, "Scorer threw on .getScore.", t);
}
}
if (DBG) Slog.v(TAG, "Final score is " + score + ".");
}

// add extra to indicate score modified by NotificationScorer
notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED,
score != initialScore);

// blocked apps
if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
if (!isSystemNotification) {
score = JUNK_SCORE;
Slog.e(TAG, "Suppressing notification from package " + pkg
+ " by user request.");
}
}

if (DBG) {
Slog.v(TAG, "Assigned score=" + score + " to " + notification);
}

if (score < SCORE_DISPLAY_THRESHOLD) {
// Notification will be blocked because the score is too low.
return;
}

// Should this notification make noise, vibe, or use the LED?
final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD);

synchronized (mNotificationList) {
final StatusBarNotification n = new StatusBarNotification(
pkg, id, tag, callingUid, callingPid, score, notification, user);
NotificationRecord r = new NotificationRecord(n);
NotificationRecord old = null;

int index = indexOfNotificationLocked(pkg, tag, id, userId);
if (index < 0) {
mNotificationList.add(r);
} else {
old = mNotificationList.remove(index);
mNotificationList.add(index, r);
// Make sure we don't lose the foreground service state.
if (old != null) {
notification.flags |=
old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
}
}

// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR;
}

final int currentUser;
final long token = Binder.clearCallingIdentity();
try {
currentUser = ActivityManager.getCurrentUser();
} finally {
Binder.restoreCallingIdentity(token);
}

if (notification.icon != 0) {
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.updateNotification(r.statusBarKey, n);
}
finally {
Binder.restoreCallingIdentity(identity);
}
} else {
long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
&& canInterrupt) {
mAttentionLight.pulse();
}
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
// Send accessibility events only for the current user.
if (currentUser == userId) {
sendAccessibilityEvent(notification, pkg);
}

notifyPostedLocked(r);
} else {
Slog.e(TAG, "Not posting notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(old.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}

notifyRemovedLocked(r);
}
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid notifications
Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+ n.getPackageName());
}

// If we're not supposed to beep, vibrate, etc. then don't.
if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& (r.getUserId() == UserHandle.USER_ALL ||
(r.getUserId() == userId && r.getUserId() == currentUser))
&& canInterrupt
&& mSystemReady) {

final AudioManager audioManager = (AudioManager) mContext
.getSystemService(Context.AUDIO_SERVICE);

// sound

// should we use the default notification sound? (indicated either by
// DEFAULT_SOUND or because notification.sound is pointing at
// Settings.System.NOTIFICATION_SOUND)
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
Settings.System.DEFAULT_NOTIFICATION_URI
.equals(notification.sound);

Uri soundUri = null;
boolean hasValidSound = false;

if (useDefaultSound) {
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;

// check to see if the default notification sound is silent
ContentResolver resolver = mContext.getContentResolver();
hasValidSound = Settings.System.getString(resolver,
Settings.System.NOTIFICATION_SOUND) != null;
} else if (notification.sound != null) {
soundUri = notification.sound;
hasValidSound = (soundUri != null);
}

if (hasValidSound) {
boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
int audioStreamType;
if (notification.audioStreamType >= 0) {
audioStreamType = notification.audioStreamType;
} else {
audioStreamType = DEFAULT_STREAM_TYPE;
}
mSoundNotification = r;
// do not play notifications if stream volume is 0 (typically because
// ringer mode is silent) or if there is a user of exclusive audio focus
if ((audioManager.getStreamVolume(audioStreamType) != 0)
&& !audioManager.isAudioFocusExclusive()) {
final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioService.getRingtonePlayer();
if (player != null) {
player.playAsync(soundUri, user, looping, audioStreamType);
}
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}

// vibrate
// Does the notification want to specify its own vibration?
final boolean hasCustomVibrate = notification.vibrate != null;

// new in 4.2: if there was supposed to be a sound and we're in vibrate
// mode, and no other vibration is specified, we fall back to vibration
final boolean convertSoundToVibration =
!hasCustomVibrate
&& hasValidSound
&& (audioManager.getRingerMode()
== AudioManager.RINGER_MODE_VIBRATE);

// The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;

if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
&& !(audioManager.getRingerMode()
== AudioManager.RINGER_MODE_SILENT)) {
mVibrateNotification = r;

if (useDefaultVibrate || convertSoundToVibration) {
// Escalate privileges so we can use the vibrator even if the
// notifying app does not have the VIBRATE permission.
long identity = Binder.clearCallingIdentity();
try {
mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
useDefaultVibrate ? mDefaultVibrationPattern
: mFallbackVibrationPattern,
((notification.flags & Notification.FLAG_INSISTENT) != 0)
? 0: -1);
} finally {
Binder.restoreCallingIdentity(identity);
}
} else if (notification.vibrate.length > 1) {
// If you want your own vibration pattern, you need the VIBRATE
// permission
mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
notification.vibrate,
((notification.flags & Notification.FLAG_INSISTENT) != 0)
? 0: -1);
}
}
}

// light
// the most recent thing gets the light
mLights.remove(old);
if (mLedNotification == old) {
mLedNotification = null;
}
//Slog.i(TAG, "notification.lights="
//        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS)
//                  != 0));
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
&& canInterrupt) {
mLights.add(r);
updateLightsLocked();
} else {
if (old != null
&& ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) {
updateLightsLocked();
}
}
}
}
});

idOut[0] = id;
}
此时mDisabledNotifications,mSoundNotification,mVibrateNotification全部现身了,对声音,震动,进行控制。

提示音播放流程

enqueueNotificationInternal内部播放音频时,先获取sound 的Uri资源路径,设置音频类型,获取音频服务,获取player,最后播放
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;  // 默认音频

soundUri = notification.sound;  // 上层传递下来

audioStreamType = notification.audioStreamType; // 设置音频流类型

final IRingtonePlayer player = mAudioService.getRingtonePlayer();  // 获取铃声播放器

player.playAsync(soundUri, user, looping, audioStreamType); // 播放铃声
至此,整个USB拔插时,铃声的播放流程分析完毕,熟悉了流程就很容易修改铃声,定制notification的行为.

Notification行为分析

上面分析了相关流程,此处对notification发生产生了哪些行为,我们可以看看函数dump和

protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump NotificationManager from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
}

pw.println("Current Notification Manager state:");

pw.println("  Listeners (" + mEnabledListenersForCurrentUser.size()
+ ") enabled for current user:");
for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
pw.println("    " + cmpt);
}

pw.println("  Live listeners (" + mListeners.size() + "):");
for (NotificationListenerInfo info : mListeners) {
pw.println("    " + info.component
+ " (user " + info.userid + "): " + info.listener
+ (info.isSystem?" SYSTEM":""));
}

int N;

synchronized (mToastQueue) {
N = mToastQueue.size();
if (N > 0) {
pw.println("  Toast Queue:");
for (int i=0; i<N; i++) {
mToastQueue.get(i).dump(pw, "    ");
}
pw.println("  ");
}

}

synchronized (mNotificationList) {
N = mNotificationList.size();
if (N > 0) {
pw.println("  Notification List:");
for (int i=0; i<N; i++) {
mNotificationList.get(i).dump(pw, "    ", mContext);
}
pw.println("  ");
}

N = mLights.size();
if (N > 0) {
pw.println("  Lights List:");
for (int i=0; i<N; i++) {
pw.println("    " + mLights.get(i));
}
pw.println("  ");
}

pw.println("  mSoundNotification=" + mSoundNotification);
pw.println("  mVibrateNotification=" + mVibrateNotification);
pw.println("  mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
pw.println("  mSystemReady=" + mSystemReady);
pw.println("  mArchive=" + mArchive.toString());
Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
int i=0;
while (iter.hasNext()) {
pw.println("    " + iter.next());
if (++i >= 5) {
if (iter.hasNext()) pw.println("    ...");
break;
}
}

}
}

dump函数就做的其实就是指令 dumpsys notification 显示的内容
private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
// tell the app
if (sendDelete) {
if (r.getNotification().deleteIntent != null) {
try {
r.getNotification().deleteIntent.send();
} catch (PendingIntent.CanceledException ex) {
// do nothing - there's no relevant way to recover, and
//     no reason to let this propagate
Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex);
}
}
}

// status bar
if (r.getNotification().icon != 0) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(r.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}
r.statusBarKey = null;
notifyRemovedLocked(r);
}

// sound
if (mSoundNotification == r) {
mSoundNotification = null;
final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioService.getRingtonePlayer();
if (player != null) {
player.stopAsync();
}
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
}
}

// vibrate
if (mVibrateNotification == r) {
mVibrateNotification = null;
long identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}

// light
mLights.remove(r);
if (mLedNotification == r) {
mLedNotification = null;
}

// Save it for users of getHistoricalNotifications()
mArchive.record(r.sbn);
}
就是对状态栏,声音,震动,光进行了处理,并且mArchive记录了所有的notification,如果需要配合新增的传感器也添加类型的行为动作。

相关notification学习链接:
http://blog.csdn.net/feng88724/article/details/6259071 http://blog.csdn.net/lizhiguo0532/article/details/7515007 http://www.cnblogs.com/stay/articles/1898963.html http://yuanyao.iteye.com/blog/472332
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: