关闭

Android U盘拔插提示音分析

标签: notificationUSBNotificationManager
1246人阅读 评论(1) 收藏 举报
分类:


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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:176745次
    • 积分:2625
    • 等级:
    • 排名:第13926名
    • 原创:80篇
    • 转载:63篇
    • 译文:0篇
    • 评论:18条
    文章分类
    最新评论