Android U盘拔插提示音分析
来源:互联网 发布:软件系统测试报告用途 编辑:程序博客网 时间:2024/05/14 23:22
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
0 0
- Android U盘拔插提示音分析
- 电话按键音分析号码
- Android中保存图片到U盘提示Permission denied
- U盘提示格式化
- U盘提示格式化
- VBS U盘插入提示
- 下载 repo repo init -u git://android.git.kernel.org/platform/manifest.git 提示ioerror
- 登陆SERV-U FTP的提示信息
- 登陆SERV-U FTP的提示信息
- 登陆SERV-U FTP的提示信息
- u盘打不开 提示请插入磁盘
- U盘打开总是提示格式化怎么办
- DELPHI U盘插入拔出提示
- U盘插入电脑提示格式化怎么办
- U盘装系统 提示没驱动
- android u-boot分析
- Android 4.4 U盘挂载
- android 读取U盘
- Monkey测试简介
- input file
- Android中的显示单位
- 如何制作手机自适应网页
- Downloadmanager in android
- Android U盘拔插提示音分析
- 数据结构(C++实现):栈的运用--中缀表达式转换为后缀表达式既 nyoj 257
- AsyncTask同步加载基础
- TCP协议中的三次握手和四次挥手
- Vlan Mapping和Mux Vlan配置
- Android CrashHandler使用时存在多个Log文件问题
- const;函数(默认值);内存管理
- Cucumber
- SQLSTATE[HY000]: General error