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>
这个表示是蓝灯;具体想改成其他值;- #ff0000ff 表示蓝灯
- #ff00ff00 表示绿灯
- #ffff0000 表示红灯
拓展:
如果想实现屏幕亮的时候,指示灯灭,屏幕灭的时候指示灯亮;可以监听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代码调用底层的接口实现灯的颜色和闪烁的变化的;