1. 程式人生 > >Android 呼吸燈流程分析(一)

Android 呼吸燈流程分析(一)

一、Android 呼吸燈的使用

     在講呼吸燈實現流程之前,我們先看一下如何使用它。
     Android提供了呼吸燈的介面,我們可以通過該介面,控制呼吸燈的閃爍頻率和佔空比。具體程式碼如下:

package com.example.test;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;

public class MainActivity extends Activity {
	Button working;
	EditText ledOn;
	EditText ledOff;	
	
	final int ID_LED=19871103;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		ledOn = (EditText)findViewById(R.id.LedOn);
		ledOff = (EditText)findViewById(R.id.LedOff);
		working = (Button)findViewById(R.id.bu1);
		working.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				NotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
				Notification notification = new Notification();
				notification.ledARGB = 0xffff0000; //這裡是顏色,我們可以嘗試改變,理論上0xFFff0000是紅色
//				notification.ledOnMS = 350;
//				notification.ledOffMS = 300;
				notification.ledOnMS = Integer.parseInt((ledOn.getText().toString()));
				notification.ledOffMS = Integer.parseInt((ledOff.getText().toString()));
				notification.flags = Notification.FLAG_SHOW_LIGHTS;
				nm.notify(ID_LED, notification);
			}
		});
		//nm.cancel(ID_LED);
	}
}
      通過該程式,便能自由控制呼吸燈的佔空比與頻率。

二、Android上層呼吸燈的實現

     1、NotificationManager

      (1).在apk中我們填充了結構notification,並呼叫了nm.notify。很顯然,找到了關鍵點NotificationManager,對應檔案為:
              frameworks/base/core/java/android/app/NotificationManager.java

      (2).在NotificationManager.java中找到了我們的呼叫方法notify。 對應如下: 

public void notify(int id, Notification notification)
107    {
108        notify(null, id, notification);
109    }
110
111    /**
112     * Post a notification to be shown in the status bar. If a notification with
113     * the same tag and id has already been posted by your application and has not yet been
114     * canceled, it will be replaced by the updated information.
115     *
116     * @param tag A string identifier for this notification.  May be {@code null}.
117     * @param id An identifier for this notification.  The pair (tag, id) must be unique
118     *        within your application.
119     * @param notification A {@link Notification} object describing what to
120     *        show the user. Must not be null.
121     */
122    public void notify(String tag, int id, Notification notification)
123    {
124        int[] idOut = new int[1];
125        INotificationManager service = getService();
126        String pkg = mContext.getPackageName();
127        if (notification.sound != null) {
128            notification.sound = notification.sound.getCanonicalUri();
129        }
130        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
131        try {
132            service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
133                    UserHandle.myUserId());
134            if (id != idOut[0]) {
135                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
136            }
137        } catch (RemoteException e) {
138        }
139    }
      抓取到關鍵點:enqueueNotificationWithTag。它位於類NotificationManagerService.java中,具體位置如下:

      frameworks/base/services/java/com/android/server/NotificationManagerService.java
      (3).NotificationManagerService

       進入類NotificationManagerService.java,找到我們在NotificationManager.java中呼叫的方法:enqueueNotificationWithTag,如下:

 public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
956            int[] idOut, int userId)
957    {
958        enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
959                tag, id, notification, idOut, userId);
960    }
961
962    private final static int clamp(int x, int low, int high) {
963        return (x < low) ? low : ((x > high) ? high : x);
964    }
965
966    // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
967    // uid/pid of another application)
968    public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
969            String tag, int id, Notification notification, int[] idOut, int userId)
970    {
971        if (DBG) {
972            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
973        }
974        checkCallerIsSystemOrSameApp(pkg);
975        final boolean isSystemNotification = ("android".equals(pkg));
976
977        userId = ActivityManager.handleIncomingUser(callingPid,
978                callingUid, userId, true, false, "enqueueNotification", pkg);
979        UserHandle user = new UserHandle(userId);
980
981        // Limit the number of notifications that any given package except the android
982        // package can enqueue.  Prevents DOS attacks and deals with leaks.
983        if (!isSystemNotification) {
        很顯然我們進入了:enqueueNotificationInternal,該函式太長,不復制了就,~_~。這個函式中實現了不少功能,如是否播放聲音,是否震動。最後找到控制呼吸燈的位置在這個方法中:
1321            if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
1322                    && canInterrupt) {
1323                mLights.add(r);
1324                updateLightsLocked();
1325            } else {
1326                if (old != null
1327                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
1328                    updateLightsLocked();
1329                }
1330            }
        很顯然,關鍵點就是:updateLightsLocked()。進入之後會有一系列判斷、賦值之類操作。之後進入:
               private LightsService.Light mNotificationLight;

1602            if (mNotificationPulseEnabled) {
1603                // pulse repeatedly
1604            	///M: log lights information
1605            	Log.d(TAG, "notification setFlashing ledOnMS = "+ledOnMS + " ledOffMS = "+ ledOffMS);
1606                mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
1607                        ledOnMS, ledOffMS);
1608                ///M:
1609            } else {
1610                // pulse only once
1611                mNotificationLight.pulse(ledARGB, ledOnMS);
1612            }
         然後,我們開始進入LightsService。

       (4).LightsService

       LightsService的位置如下:
                   frameworks/base/services/java/com/android/server/LightsService.java
      在LightsService中,通過方法:setFlashing 呼叫:setLightLocked,最終到達了JNI的setLight_native;

116        private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
117            if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) {
118                if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
119                        + Integer.toHexString(color));
120                mColor = color;
121                mMode = mode;
122                mOnMS = onMS;
123                mOffMS = offMS;
124                setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
125            }
126        }
       2.JNI

      通過方法setLight_native,進入到了JNI層中,位置如下:
                      frameworks/base/services/jni/com_android_server_LightsService.cpp
      在方法:setLight_native中,一樣的進行了相關的判斷、接受上層賦值.之後根據引數,呼叫了對應的set_light:

127    ALOGD("setLight_native: light=%d, colorARGB=0x%x, flashMode=%d, onMS=%d, offMS=%d, brightnessMode=%d",
128	light, colorARGB, flashMode, onMS, offMS, brightnessMode);
129
130#if defined(MTK_AAL_SUPPORT)
131    if (light == LIGHT_INDEX_BACKLIGHT) {
132        if (AALClient::getInstance().setBacklightColor(colorARGB & 0x00ffffff) == 0)
133            return;
134        ALOGW("Fail to set backlight from AAL service");
135    }
136#endif
137
138    devices->lights[light]->set_light(devices->lights[light], &state);
      最後通過set_light轉入了HAL層。

      3.HAL

     呼吸燈的HAL層對應位置如下:
                    mediatek/hardware/liblights/lights.c
    我們在NotificationManager下來的set_light對應為:
                    open_lights下的:

584    }
585    else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
586        set_light = set_light_notifications;
587    }
      進入set_light_notifications,通過如下呼叫:
                  set_light_notifications ----> handle_speaker_battery_locked ---->  set_speaker_light_locked
       在函式set_speaker_light_locked中,最後判斷我們要控制的led是red,green還是blue,從我的範例上看,我傳入的led引數為0xffff0000,對應為red。於是,進入如下的          red:
465    if (red) {
466        blink_green(0, 0, 0);
467        blink_blue(0, 0, 0);
468        blink_red(red, onMS, offMS);
469    }
470    else if (green) {
471        blink_red(0, 0, 0);
472        blink_blue(0, 0, 0);
473        blink_green(green, onMS, offMS);
474    }
475    else if (blue) {
476        blink_red(0, 0, 0);
477        blink_green(0, 0, 0);
478        blink_blue(blue, onMS, offMS);
479    }
480    else {
481        blink_red(0, 0, 0);
482        blink_green(0, 0, 0);
483        blink_blue(0, 0, 0);
484    }
         進入了red函式之後,重點如下:
                 上層傳下來的的level值為0,則直接關閉RED_LED_FILE(char const*const RED_LED_FILE = "/sys/class/leds/red/brightness")
 
248	if (nowStatus == 0) { 
249        	write_int(RED_LED_FILE, 0);
250	}
         上層傳下的來的引數onMS和offMS都有值,則呼吸燈閃爍:
251	else if (nowStatus == 1) {
252//        	write_int(RED_LED_FILE, level); // default full brightness
253		write_str(RED_TRIGGER_FILE, "timer");    
254		while (((access(RED_DELAY_OFF_FILE, F_OK) == -1) || (access(RED_DELAY_OFF_FILE, R_OK|W_OK) == -1)) && i<10) {
255			ALOGD("RED_DELAY_OFF_FILE doesn't exist or cannot write!!\n");
256			led_wait_delay(5);//sleep 5ms for wait kernel LED class create led delay_off/delay_on node of fs
257			i++;
258		}
259		write_int(RED_DELAY_OFF_FILE, offMS);
260		write_int(RED_DELAY_ON_FILE, onMS);
261	}
         其他情況下,我們直接就點亮紅色呼吸燈:
262	else {
263		write_str(RED_TRIGGER_FILE, "none");
264        	write_int(RED_LED_FILE, 255); // default full brightness
265	}
       4.小結

       到處Andoid上層的呼吸燈基本上就這個。
      在HAL中最後,點亮,關閉和閃爍呼吸燈,點亮和關閉呼吸燈都是直接操作裝置介面: RED_LED_FILE = "/sys/class/leds/red/brightness";
      閃爍則相對複雜一些,接下來驅動部分就以閃爍為為範例進行講解。