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
原创粉丝点击