Android按键灯,指示灯总结【Android源码解析十一】

       Android中有各种灯,背光灯,按键灯,指示灯,等等;前几天修改了这部分代码,整理下思路,其实都不难;

       首先,来说说指示灯(提示灯),即未接电话,未接短信的时候,会闪灯,这个其实就是NotificationManager这个类中的notify()方法来处理的;流程简单来过一下:


       Step 1:从应用层发送的notify(),到framework层被NotificationManager.java这个类接受了,来看看这个notify()这个方法:

public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }

 public void notify(String tag, int id, Notification notification)
    {
        int[] idOut = new int[1];
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        try {
            service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
                    UserHandle.myUserId());
            if (id != idOut[0]) {
                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
            }
        } catch (RemoteException e) {
        }
    }
重点关注这个enqueueNotificationWithTag()这个方法,这个方法首先会取是否传递过来了声音的Uri,如果传递了,就保存下来,给等会播放用;


     Step 2:enqueueNotificationWithTag()这个方法调用到了NotificationManagerService.java这个类中去了,来看看这个方法:

public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
            int[] idOut, int userId)
    {
        enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
                tag, id, notification, idOut, userId);
    }

// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
    // uid/pid of another application)
    public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
            String tag, int id, Notification notification, int[] idOut, int userId)
    {
        if (DBG) {
            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification + ", notification.ledARGB : "+ notification.ledARGB);
        }
        checkCallerIsSystemOrSameApp(pkg);
        final boolean isSystemNotification = ("android".equals(pkg));

        userId = ActivityManager.handleIncomingUser(callingPid,
                callingUid, userId, true, false, "enqueueNotification", pkg);
        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;
                int eldestIdx = 0;
                long eldestTime = 0;
                final int N = mNotificationList.size();
                for (int i=0; i<N; i++) {
                    NotificationRecord r = mNotificationList.get(i);
                    if (r.pkg.equals(pkg) && r.userId == userId) {
                        if (count == 0) {
                            eldestTime = r.notification.when;
                            eldestIdx = i;
                        }
                        else {
                            if (r.notification.when < eldestTime) {
                                eldestTime = r.notification.when;
                                eldestIdx = i;
                            }
                        }
                        count++;
                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                            Slog.e(TAG, "Package has already posted " + count
                                    + " notifications.  Not showing more.  package=" + pkg);
                            //return;
                            // [ALPS00447419] SystemUI OOM: remove eldest entry
                            r = mNotificationList.get(eldestIdx);
                            mNotificationList.remove(eldestIdx);
                            cancelNotificationLocked(r, true);
                            break;
                        }
                    }
                }
            }
        }

        // 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);
            }
        }

        // === 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

        // blocked apps
        if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
            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) {
            NotificationRecord r = new NotificationRecord(pkg, tag, id, 
                    callingUid, callingPid, userId,
                    score,
                    notification);
            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.notification.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) {
                final StatusBarNotification n = new StatusBarNotification(
                        pkg, id, tag, r.uid, r.initialPid, score, notification, user);
                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.notification.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);
                }
            } else {
                Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
                if (old != null && old.statusBarKey != null) {
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mStatusBar.removeNotification(old.statusBarKey);
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            }

            // If we're not supposed to beep, vibrate, etc. then don't.
            // ensure mms can send notification when a phone is calling
            if ((((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) || pkg.equals("com.android.mms"))
                    && (!(old != null
                        && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
                    && (r.userId == UserHandle.USER_ALL ||
                        (r.userId == userId && r.userId == currentUser))
                    && canInterrupt
                    && mSystemReady) {
            	///M: 
            	if (DBG) {
                    Log.d(TAG,"pakage="+pkg+",In NotificationMangerService, this notification soud, leds and vibrate enable");
                }
                final AudioManager audioManager = (AudioManager) mContext
                .getSystemService(Context.AUDIO_SERVICE);

                // sound
                final boolean useDefaultSound =
                    (notification.defaults & Notification.DEFAULT_SOUND) != 0;
                ///M: log sound information 
            	if (DBG) {
					Log.d(TAG,"useDefaultSound="+useDefaultSound);
					Log.d(TAG,"notification.sound="+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;
                    ///M: log sound information
                    if (DBG) {
						Log.d(TAG,"looping="+looping);
						Log.d(TAG,"audioStreamType="+audioStreamType);
						Log.d(TAG,"StreamVolume="+audioManager.getStreamVolume(audioStreamType));
				    }
                    // do not play notifications if stream volume is 0
                    // (typically because ringer mode is silent) or if speech recognition is active.
                    if ((audioManager.getStreamVolume(audioStreamType) != 0)
                            && !audioManager.isSpeechRecognitionActive()) {
                        final long identity = Binder.clearCallingIdentity();
                        try {
                            final IRingtonePlayer player = mAudioService.getRingtonePlayer();
                            if (player != null) {
                                ///M: [ALPS00461691]No notification sound when it detects wifi networks
                                if (user.getIdentifier() == UserHandle.USER_ALL) {
                                    user = UserHandle.OWNER;
                                }
                                player.playAsync(soundUri, user, looping, audioStreamType);
                            }
                        } catch (RemoteException e) {
                        } finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    }
                }

                // vibrate
                ///M: for device manager 
                if (DBG) {
		            Log.d(TAG,"mDmLock="+mDmLock);
		        }
                if (mDmLock == false){
                // 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 (DBG) {
                        Log.w(TAG, "set vibrate!");
                    }

                    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(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(notification.vibrate,
                            ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
                    }
                }
                } // mDmLock == false
            }

            // this option doesn't shut off the lights

            // 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.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
                    updateLightsLocked();
                }
            }
        }

        idOut[0] = id;
    }
上面这个方法做的操作有点多,代码有300多行。其中有设计播放声音的地方:

 player.playAsync(soundUri, user, looping, audioStreamType);
有是否播放震动的地方:

 mVibrator.vibrate(notification.vibrate,
                            ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
最后闪灯的地方在这个逻辑中:

 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
                    && canInterrupt) {
                mLights.add(r);
                updateLightsLocked();
            } 
重点看updateLightsLocked()这个方法,这个方法里面有逻辑的操作;


       Step 3:updateLightsLocked()这个方法的代码如下:

// lock on mNotificationList
    private void updateLightsLocked()
    {
        // handle notification lights
        if (mLedNotification == null) {
            // get next notification, if any
            int n = mLights.size();
            if (n > 0) {
                mLedNotification = mLights.get(n-1);
            }
        }

        // Don't flash while we are in a call or screen is on
        ///M: we need flash when screen is on
        //mScreenOn add by lvmingfei for don't flash while screen is on in 2013-09-20
        if (mLedNotification == null || mInCall || mCallRinging)) {
            mNotificationLight.turnOff();
        } else {
            int ledARGB = mLedNotification.notification.ledARGB;
            int ledOnMS = mLedNotification.notification.ledOnMS;
            int ledOffMS = mLedNotification.notification.ledOffMS;
            if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
                ledARGB = mDefaultNotificationColor;
                ledOnMS = mDefaultNotificationLedOn;
                ledOffMS = mDefaultNotificationLedOff;
            }
            if (mNotificationPulseEnabled) {
                // pulse repeatedly
            	///M: log lights information 
            	Log.d(TAG, "notification setFlashing ledOnMS = "+ledOnMS + " ledOffMS = "+ ledOffMS + ", ledARGB :" + ledARGB);
                mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
                        ledOnMS, ledOffMS);
                ///M: 
            } else {
                // pulse only once
                mNotificationLight.pulse(ledARGB, ledOnMS);
            }
        }
    }
这段代码中有如下操作,判断一些变量的状态,以决定时候关闭指示灯,还是闪光,还是只闪一次的操作;
电话的状态是否是offhook,或来电的状态,会操作:
mNotificationLight.turnOff();
下面这个条件也很重要:
if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
未解来电,或短信的时候这个值会设置为
notification.defaults |= Notification.DEFAULT_LIGHTS;
这个值是4,那么上面的这个判断就为true了,所以灯的颜色就不是由
mLedNotification.notification.ledARGB;
这个值决定的了,而是由mDefaultNotificationColor这个值决定的,这个值的定义在

mDefaultNotificationColor = resources.getColor(
                com.android.internal.R.color.config_defaultNotificationColor);
这个值的定义在framework/base/core/res/res/values/config.xml中定义的,

<color name="config_defaultNotificationColor">#ff0000ff</color>
这个表示是蓝灯;具体想改成其他值;

  1. #ff0000ff 表示蓝灯
  2. #ff00ff00 表示绿灯
  3. #ffff0000 表示红灯
后面的逻辑就调用到LightsService.java中去了,这个类是管理灯的类,(背光灯,按键灯,指示灯等等);


     拓展:

             如果想实现屏幕亮的时候,指示灯灭,屏幕灭的时候指示灯亮;可以监听ACTION_SCREEN_ON/ACTION_SCREEN_OFF的广播,搞一个全局的变量控制下;再调用updateNotificationPulse()这个方法,把变量的判断加载updateNotificationPulse()这个方法的灯亮灭判断的地方即可;


        其次,我们来看看返回键的灯,即按键灯;

        Step 1 :先来看看PowerManagerService.java这个类。按键灯的定义

 private LightsService.Light mButtonLight;
        初始化方法:
mButtonLight = mLightsService.getLight(LightsService.LIGHT_ID_BUTTONS);
 if ( (newScreenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT) && (mWakefulness == WAKEFULNESS_AWAKE) && !mIPOShutdown && !mShutdownFlag) {
                if ( ( (mWakeLockSummary & WAKE_LOCK_BUTTON_BRIGHT) != 0 ) ||
                        ( (mUserActivitySummary & USER_ACTIVITY_BUTTON_BRIGHT) != 0) ) {
                    mButtonLight.setBrightness(mScreenBrightness);
                    Slog.i(TAG, "setBrightness mButtonLight, mScreenBrightness=" + mScreenBrightness);
                } else {
                    mButtonLight.turnOff();
                    Slog.i(TAG, "setBrightness mButtonLight 0 ===.");
                }
            } else {
                mButtonLight.turnOff();
                Slog.i(TAG, "setBrightness mButtonLight else 0.");
            }
灯的点亮的方法setBrightness()

灯关闭的方法turnOff()

要想修改按键灯随p-sensor的灯的亮灭同步,可以参考Android4.2中Phone的P-sensor的应用的分析。

然后再加上上述控制灯亮灭的方法就可实现同步;


     总结:灯的亮灭最后都会调用到LightsService.java这个类的,最后通过c代码调用底层的接口实现灯的颜色和闪烁的变化的;
















你可能感兴趣的:(Android按键灯,指示灯总结【Android源码解析十一】)