Android System Server大綱之LightsService
Android閃光燈
Android Led
前言
從功能機以來,手機等設備就配備了Led閃光燈,當有未接電話、未讀短信和通知等等,Led就會閃爍。和振動器類似,都是給用戶一種人機交互的反饋,哪怕這種人機交互是那麼的簡單。既然它是一個Led,也就是一個硬件,Android系統上層驅動這個硬件的服務就是LightsService,所以,這個文章也是描述一個軟硬件結合的功能。但是上層APP不能直接驅動Led硬件,LightsService是系統所使用。
既然是軟硬結合的一個功能,那麼在Android系統裏,從上到下實現這個功能的架構如下:
LightsService初始化
回顧《Android系統之System Server大綱 》一文,LightsService在frameworks/base/services/java/com/android/server/SystemServer.java中的啓動過程是:
public final class SystemServer {
// Manages LEDs and display backlight so we need it to bring up the display.
mSystemServiceManager.startService(LightsService.class);
}
從上面的代碼中,LightsService是通過SystemServiceManager.startService()的方式啓動,在《Android系統之System Server大綱 》一文中提到的Android系統的各種服務的啓動方式可知,LightsService是繼承了SystemService,LightsService啓動後會回調onStart()方法。代碼如下:
public class LightsService extends SystemService {
@Override
public void onStart() {
publishLocalService(LightsManager.class, mService);
}
}
上述代碼在frameworks/base/services/core/java/com/android/server/lights/LightsService.java中
在《Android系統之System Server大綱 》一文中提到,publishLocalService()的作用,這裏是把mService推進LocalServices中,所以外部引用LightsService時,實際是拿到mService這個對象。mService的代碼如下:
public class LightsService extends SystemService {
private final LightsManager mService = new LightsManager() {
@Override
public Light getLight(int id) {
if (id < LIGHT_ID_COUNT) {
return mLights[id];
} else {
return null;
}
}
};
}
mService的實質是一個LightsManager,LightsManager只提供一個方法getLight(int id),從數組mLights中匹配一個返回值,返回值是什麼類型呢?看mLights[]的本質,如下:
public class LightsService extends SystemService {
final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT];
}
mLights[]的實質一個LightImpl的數組,所以LightsManager的getLight(int id)返回的是一個LightImpl的實例,LightImpl的代碼如下:
public class LightsService extends SystemService {
private final class LightImpl extends Light {
......
@Override
public void setFlashing(int color, int mode, int onMS, int offMS) {
synchronized (this) {
setLightLocked(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER);
}
}
......
}
}
LightImpl實際是Light的子類,而Light實質是一個led硬件的抽象,那麼一個led就被抽象成一個Light對象。因此,要控制led,只要拿到led的抽象對象Light即可實現驅動led。
回頭再看看LightsService的初始化,LightsService的構造方法如下:
public class LightsService extends SystemService {
public LightsService(Context context) {
super(context);
mNativePointer = init_native();
for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) {
mLights[i] = new LightImpl(i);
}
}
}
上文提到的LightImpl數組mLights就是在LightsService中被初始化了,這裏會根據LightsManager.LIGHT_ID_COUNT的總數循環生成LightImpl的實例保存在mLights數組中。這裏的LightsManager.LIGHT_ID_COUNT的值是8,代碼如下:
public abstract class LightsManager {
public static final int LIGHT_ID_BACKLIGHT = 0;
public static final int LIGHT_ID_KEYBOARD = 1;
public static final int LIGHT_ID_BUTTONS = 2;
public static final int LIGHT_ID_BATTERY = 3;
public static final int LIGHT_ID_NOTIFICATIONS = 4;
public static final int LIGHT_ID_ATTENTION = 5;
public static final int LIGHT_ID_BLUETOOTH = 6;
public static final int LIGHT_ID_WIFI = 7;
public static final int LIGHT_ID_COUNT = 8;
public abstract Light getLight(int id);
}
從LIGHT_ID_BACKLIGHT到LIGHT_ID_WIFI共8個,這裏是否表示8個led呢,後文在論述清楚。回到LightsService的構造方法,調用了init_native()方法,當然,熟悉Android架構的都清楚,其它硬件的服務和LightsService一樣,都有這麼一個過程。目的就是LightsService啓動時,調用init_native()初始化硬件,也就是軟硬件的通道在init_native()這個方法中打通。init_native()是一個native方法,對應的JNI接口是:
static jlong init_native(JNIEnv* /* env */, jobject /* clazz */)
{
int err;
hw_module_t* module;
Devices* devices;
devices = (Devices*)malloc(sizeof(Devices));
err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
......
devices->lights[LIGHT_INDEX_NOTIFICATIONS]
= get_device(module, LIGHT_ID_NOTIFICATIONS);
......
} else {
memset(devices, 0, sizeof(Devices));
}
return (jlong)devices;
}
這個函數定義在文件frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp中。
init_native()中首先調用了hw_get_module()函數,這個函數就是打開硬件設備,這個是函數的實現過程在hardware/libhardware/hardware.c中,對Android HAL熟悉的讀者,應該很熟悉這個過程,本文也不往下贅述這個過程了。然後通過調用get_device()函數取得返回值賦值Devices的變量lights,get_device()的一個參數是LIGHT_ID_NOTIFICATIONS,這和上文中LightsManager的8個led id是對應的。Devices的變量lights的實質是:
struct Devices {
light_device_t* lights[LIGHT_COUNT];
};
light_device_t定義在hardware/libhardware/include/hardware/lights.h文件中。回到init_native()函數,接着看get_device()這個函數:
static light_device_t* get_device(hw_module_t* module, char const* name)
{
int err;
hw_device_t* device;
err = module->methods->open(module, name, &device);
if (err == 0) {
return (light_device_t*)device;
} else {
return NULL;
}
}
通過module->methods->open()打開硬件設備,這個過程本文就不再贅述了。打開設備通道後,取得light_device_t,保存在Devices的數組lights中,這和LightsService上文中的LightImpl的數組mLights也是對應的關係。
通過LightsService啓動led
APP雖然不能直接驅動led,但是led一般也是爲Android的Notification所使用,所以APP發起一個通知,led也會閃爍。本文就從Android的notification入手,分析驅動led的過程。
APP創建Notification時,可以通過如下方法指定這個notification需要led:
public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
mN.ledARGB = argb;
mN.ledOnMS = onMs;
mN.ledOffMS = offMs;
if (onMs != 0 || offMs != 0) {
mN.flags |= FLAG_SHOW_LIGHTS;
}
return this;
}
本文暫時不對這個方法進行過多的闡述,會在以後的文章中再詳細介紹Android的notification機制。當然APP如果不調用這個方法,那麼Android系統會自動爲APP的notification選取默認值。
APP的notification通知到Android系統以後,就會驅動led閃爍起來。在上文LightsService的初始化中提到,led硬件已經抽象成一個Light對象,這個對象便是LightImpl,獲取到LightImpl的實例,便可驅動led。Android的notification獲取LightImpl的代碼如下:
public class NotificationManagerService extends SystemService {
final LightsManager lights = getLocalService(LightsManager.class);
mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
}
這個類定義在文件frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java中
在上文LightsService的初始化中可知,LightsService被推進到LocalService中,所以是通過getLocalService()獲取到LightsManager,然後再通過getLight()方法,獲取到LightImpl的實例,這個過程在上文LightsService的初始化中已經闡述的很明白。獲取到了LightImpl,通過什麼接口驅動led呢?先看看LightImpl的結構:
public abstract class Light {
......
public abstract void setBrightness(int brightness);
public abstract void setBrightness(int brightness, int brightnessMode);
public abstract void setColor(int color);
public abstract void setFlashing(int color, int mode, int onMS, int offMS);
public abstract void pulse();
public abstract void pulse(int color, int onMS);
public abstract void turnOff();
}
這個類定義在文件frameworks/base/services/core/java/com/android/server/lights/Light.java中
如上面的代碼,LightImpl提供setBrightness()設置亮度、setColor設置顏色、setFlashing啓動led等7個方法。因此,Anroid notification驅動led的代碼如下:
mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED,ledOnMS, ledOffMS);
這方法定義在文件個frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java中
setFlashing()接收四個參數,第一個當然是顏色,類型是rgb;第二參數是mode,數值分別是可以是:
public static final int LIGHT_FLASH_NONE = 0;
public static final int LIGHT_FLASH_TIMED = 1;
public static final int LIGHT_FLASH_HARDWARE = 2;
這些變量定義在文件frameworks/base/services/core/java/com/android/server/lights/Light.java中
第三個和第四個是一對相反的參數,分別表示led閃爍時亮的時間,led閃爍時滅的時間。接着看setFlashing()的實現:
private final class LightImpl extends Light {
public void setFlashing(int color, int mode, int onMS, int offMS) {
synchronized (this) {
setLightLocked(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER);
}
}
}
這個方法定義在文件frameworks/base/services/core/java/com/android/server/lights/LightsService.java中
直接調用了setLightLocked(),代碼如下:
private final class LightImpl extends Light {
private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
if (!mLocked && (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS ||
mBrightnessMode != brightnessMode)) {
if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
+ Integer.toHexString(color) + ": brightnessMode=" + brightnessMode);
mLastColor = mColor;
mColor = color;
mMode = mode;
mOnMS = onMS;
mOffMS = offMS;
mLastBrightnessMode = mBrightnessMode;
mBrightnessMode = brightnessMode;
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x"
+ Integer.toHexString(color) + ")");
try {
setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
}
這個方法定義在文件frameworks/base/services/core/java/com/android/server/lights/LightsService.java中
對參數整理一遍後,調用了JNI方法setLight_native(),這裏的第二個參數便是LightsManager.LIGHT_ID_NOTIFICATIONS = 4;繼續往下看setLight_native():
static void setLight_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr,
jint light, jint colorARGB, jint flashMode, jint onMS, jint offMS, jint brightnessMode)
{
Devices* devices = (Devices*)ptr;
light_state_t state;
......
state.brightnessMode = brightnessMode;
{
ALOGD_IF_SLOW(50, "Excessive delay setting light");
devices->lights[light]->set_light(devices->lights[light], &state);
}
}
這個函數定義在文件frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp中
set_light()的定義是在文件hardware/libhardware/include/hardware/lights.h中,具體實現,就因led的硬件廠商不同而不同了,這裏以某個led的廠商爲例:
static int open_lights(const struct hw_module_t *module, char const *name,
struct hw_device_t **device)
{
struct dragon_lights *lights;
......
lights->base.set_light = set_light_backlight;
*device = (struct hw_device_t *)lights;
return 0;
}
在led通道初始化過程中,set_light()函數實際調用的是set_light_backlight函數,set_light_backlight函數如下:
static int set_light_backlight(struct light_device_t *dev,
struct light_state_t const *state)
{
struct dragon_lights *lights = to_dragon_lights(dev);
int err, brightness_idx;
int brightness = rgb_to_brightness(state);
if (brightness > 0) {
// Get the bin number for brightness (0 to kNumBrightnessLevels - 1)
brightness_idx = (brightness - 1) * kNumBrightnessLevels / 0xff;
// Get brightness level
brightness = kBrightnessLevels[brightness_idx];
}
pthread_mutex_lock(&lights->lock);
err = write_brightness(lights, brightness);
pthread_mutex_unlock(&lights->lock);
return err;
}
跟蹤到這裏,本文也就不往下跟蹤了,這些代碼都會因led廠商不同而不同。驅動led的過程就到此結束。
總結
本文闡述了LightsService的作用,從打開硬件通道和驅動硬件led閃爍起來的過程,雖然APP是不可以直接驅動led,但是led幾乎都是爲android notification所用,所以,APP發佈的notification也可以使用led,通知用戶有新的狀態。對於上文提到的led顏色,對於很多設備,是不支持設置顏色了的,設置了也只能是一種顏色,所以在這些設備上,可以說是大多數設備,這個功能基本就沒有任何用處。另外,因led硬件的不同,文章中提到的8個led id,可能有,可能沒有。