首先,来说说指示灯(提示灯),即未接电话,未接短信的时候,会闪灯,这个其实就是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;
// [ALPS00] 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: [ALPS00]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 表示红灯
后面的逻辑就调用到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代码调用底层的接口实现灯的颜色和闪烁的变化的;