Android8.0 Rild守护进程
电话是一种开放的通信渠道,任何人都可以随时向任何电话号码致电或者发送短信,因此 Android 用户需要能够轻松屏蔽骚扰电话和短信。在 Android N 推出之前,Android 用户只能依靠下载的应用来限制来自骚扰电话号码的来电和短信。但是,由于没有适当的 API 来屏蔽来电和短信,这些应用大部分要么达不到预期效果,要么用户体验不佳。
一些制造商可能会提供他们自己的开箱即用型屏蔽解决方案,但是如果用户更换设备,由于缺乏互用性,他们的屏蔽列表可能会完全丢失。最后,即便用户采用了提供此类功能的拨号应用和短信客户端,他们可能仍需在每个应用中执行屏蔽操作,才能有效屏蔽来电和短信。Android 7.0 版本引入了 BlockedNumberProvider 内容提供程序,该程序可以存储用户指定的无法通过电话通讯(通话、短信、彩信)与他们联系的电话号码列表。系统会参考屏蔽列表中的号码,限制来自这些号码的来电和短信。Android 7.0 不仅会显示屏蔽号码列表,还可让用户添加和删除号码。
1. 启动Rild后台进程
hardware\ril\rild\rild.c
int main(int argc, char **argv) { // vendor ril lib path either passed in as -l parameter, or read from rild.libpath property const char *rilLibPath = NULL; // ril arguments either passed in as -- parameter, or read from rild.libargs property char **rilArgv; //厂商ril库句柄 void *dlHandle; //厂商ril库回调 const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **); // Pointer to sap init function in vendor ril RIL_RadioFunctions *(*rilUimInit)(const struct RIL_Env *, int, char **); const char *err_str = NULL; // functions returned by ril init function in vendor ril const RIL_RadioFunctions *funcs; ...... //打开厂商库 dlHandle = dlopen(rilLibPath, RTLD_NOW); //启动事件循环 RIL_startEventLoop(); //调用系统库 rilInit =(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init"); rilUimInit =(RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_SAP_Init"); //初始化rilInit funcs = rilInit(&s_rilEnv, argc, rilArgv); //注册回调函数 RIL_register(funcs); //注册套接 if (rilUimInit) { RIL_register_socket(rilUimInit, RIL_SAP_SOCKET, argc, rilArgv); }done: ..... rilc_thread_pool(); .....}
2. 启动事件循环
hardware\ril\libril\ril.cpp
extern "C" voidRIL_startEventLoop(void) { ...... pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //启动线程,运行 eventLoop int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL); while (s_started == 0) { pthread_cond_wait(&s_startupCond, &s_startupMutex); } ......
hardware\ril\libril\ril.cpp
static void *eventLoop(void *param) { int ret; int filedes[2]; //初始化事件链表 ril_event_init(); //创建管道 ret = pipe(filedes); //读写唤醒描述符 s_fdWakeupRead = filedes[0]; s_fdWakeupWrite = filedes[1]; //执行 fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK); //设置事件描述符和事件处理函数 ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL); rilEventAddWakeup (&s_wakeupfd_event); //开始ril事件循环 ril_event_loop(); ...... return NULL;}
hardware\ril\libril\ril_event.cpp
void ril_event_init(){ MUTEX_INIT(); FD_ZERO(&readFds); init_list(&timer_list); //初始化事件链表 init_list(&pending_list); memset(watch_table, 0, sizeof(watch_table));}static void init_list(struct ril_event * list){ memset(list, 0, sizeof(struct ril_event)); list->next = list; list->prev = list; list->fd = -1; //存储文件描述符}
hardware\ril\libril\ril_event.cpp
void ril_event_loop(){ int n; fd_set rfds; struct timeval tv; struct timeval * ptv; for (;;) { ...... n = select(nfds, &rfds, NULL, NULL, ptv); //开始select ...... // Check for timeouts processTimeouts(); // Check for read-ready processReadReadies(&rfds, n); // Fire away firePending(); }}static void processReadReadies(fd_set * rfds, int n){ for (int i = 0; (i < MAX_FD_EVENTS) && (n > 0); i++) { struct ril_event * rev = watch_table[i]; if (rev != NULL && FD_ISSET(rev->fd, rfds)) { addToList(rev, &pending_list); //添加到挂起列表 if (rev->persist == false) { removeWatch(rev, i); //从描述符观察表中移除 } n--; } }}static void firePending(){ struct ril_event * ev = pending_list.next; while (ev != &pending_list) { struct ril_event * next = ev->next; removeFromList(ev); ev->func(ev->fd, 0, ev->param); //执行函数 processWakeupCallback ev = next; }}
hardware\ril\libril\ril.cpp
static void processWakeupCallback(int fd, short flags, void *param) { char buff[16]; int ret; //循环从套接端读取 do { ret = read(s_fdWakeupRead, &buff, sizeof(buff)); } while (ret > 0 || (ret < 0 && errno == EINTR));}
hardware\ril\libril\ril.cpp
static void rilEventAddWakeup(struct ril_event *ev) { ril_event_add(ev); triggerEvLoop();}
hardware\ril\libril\ril_event.cpp
void ril_event_add(struct ril_event * ev){ for (int i = 0; i < MAX_FD_EVENTS; i++) { if (watch_table[i] == NULL) { watch_table[i] = ev; //加入事件观察表中 ev->index = i; //加索引 FD_SET(ev->fd, &readFds); if (ev->fd >= nfds) nfds = ev->fd+1; break; } }}
hardware\ril\libril\ril.cpp
static void triggerEvLoop() { int ret; if (!pthread_equal(pthread_self(), s_tid_dispatch)) { //循环触发唤醒事件循环 do { ret = write (s_fdWakeupWrite, " ", 1); } while (ret < 0 && errno == EINTR); }}
3. 启动RIL库
hardware\ril\reference-ril\reference-ril.c
const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv){ ...... //启动线程,开始主循环 ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL); ...... return &s_callbacks; //返回操作结构体}//函数回调结构表static const RIL_RadioFunctions s_callbacks = { RIL_VERSION, onRequest, currentState, onSupports, onCancel, getVersion};
hardware\ril\reference-ril\reference-ril.c
开始打开BP(基带处理器)
static void *mainLoop(void *param __unused){ int fd; int ret; at_set_on_reader_closed(onATReaderClosed); at_set_on_timeout(onATTimeout); for (;;) { fd = -1; while (fd < 0) { if (isInEmulator()) { fd = qemu_pipe_open("pipe:qemud:gsm"); //模拟 } else if (s_port > 0) { fd = socket_network_client("localhost", s_port, SOCK_STREAM); //网络 } else if (s_device_socket) { //本地 fd = socket_local_client(s_device_path, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); } else if (s_device_path != NULL) { fd = open (s_device_path, O_RDWR); //打开基带处理器 if ( fd >= 0 && !memcmp( s_device_path, "/dev/ttyS", 9 ) ) { /* disable echo on serial ports */ struct termios ios; tcgetattr( fd, &ios ); ios.c_lflag = 0; /* disable ECHO, ICANON, etc... */ tcsetattr( fd, TCSANOW, &ios ); } } ...... } s_closed = 0; ret = at_open(fd, onUnsolicited); // 开始基带处理通讯,出入文件描述符,回调处理函数 RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0); ...... }}
4. 未经请求的AT通讯
hardware\ril\reference-ril\atchannel.c
int at_open(int fd, ATUnsolHandler h){ int ret; pthread_t tid; pthread_attr_t attr; s_fd = fd; s_unsolHandler = h; //回调处理句柄 s_readerClosed = 0; //开线程循环读取 ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr); ...... return 0;}
hardware\ril\reference-ril\atchannel.c
循环读取基带处理器的AT指令
static void *readerLoop(void *arg __unused){ for (;;) { const char * line; line = readline(); //读取AT命令 if (line == NULL) { break; } if(isSMSUnsolicited(line)) { //是未经请求的回复 char *line1; const char *line2; // The scope of string returned by 'readline()' is valid only // till next call to 'readline()' hence making a copy of line // before calling readline again. line1 = strdup(line); line2 = readline(); if (line2 == NULL) { free(line1); break; } if (s_unsolHandler != NULL) { s_unsolHandler (line1, line2); //回调 } free(line1); } else { processLine(line); //处理 } } onReaderClosed(); return NULL;}
hardware\ril\reference-ril\atchannel.c
读取AT命令行
static const char *readline(){ ssize_t count; char *p_read = NULL; char *p_eol = NULL; char *ret; /* this is a little odd. I use *s_ATBufferCur == 0 to * mean "buffer consumed completely". If it points to a character, than * the buffer continues until a \0 */ if (*s_ATBufferCur == '\0') { /* empty buffer */ s_ATBufferCur = s_ATBuffer; *s_ATBufferCur = '\0'; p_read = s_ATBuffer; } else { /* *s_ATBufferCur != '\0' */ /* there's data in the buffer from the last read */ // skip over leading newlines while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n') s_ATBufferCur++; p_eol = findNextEOL(s_ATBufferCur); if (p_eol == NULL) { /* a partial line. move it up and prepare to read more */ size_t len; len = strlen(s_ATBufferCur); memmove(s_ATBuffer, s_ATBufferCur, len + 1); p_read = s_ATBuffer + len; s_ATBufferCur = s_ATBuffer; } /* Otherwise, (p_eol !- NULL) there is a complete line */ /* that will be returned the while () loop below */ } while (p_eol == NULL) { if (0 == MAX_AT_RESPONSE - (p_read - s_ATBuffer)) { RLOGE("ERROR: Input line exceeded buffer\n"); /* ditch buffer and start over again */ s_ATBufferCur = s_ATBuffer; *s_ATBufferCur = '\0'; p_read = s_ATBuffer; } do { //循环读取AT指令数据 count = read(s_fd, p_read, MAX_AT_RESPONSE - (p_read - s_ATBuffer)); } while (count < 0 && errno == EINTR); if (count > 0) { AT_DUMP( "<< ", p_read, count ); p_read[count] = '\0'; // skip over leading newlines while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n') s_ATBufferCur++; p_eol = findNextEOL(s_ATBufferCur); p_read += count; } else if (count <= 0) { /* read error encountered or EOF reached */ if(count == 0) { RLOGD("atchannel: EOF reached"); } else { RLOGD("atchannel: read error %s", strerror(errno)); } return NULL; } } /* a full line in the buffer. Place a \0 over the \r and return */ ret = s_ATBufferCur; *p_eol = '\0'; s_ATBufferCur = p_eol + 1; /* this will always be <= p_read, */ /* and there will be a \0 at *p_read */ RLOGD("AT< %s\n", ret); return ret;}
hardware\ril\reference-ril\atchannel.c
处理命令行
static void processLine(const char *line){ pthread_mutex_lock(&s_commandmutex); if (sp_response == NULL) { /* no command pending */ handleUnsolicited(line); //处理未经请求的回复 } else if (isFinalResponseSuccess(line)) { sp_response->success = 1; handleFinalResponse(line); } else if (isFinalResponseError(line)) { sp_response->success = 0; handleFinalResponse(line); } else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) { // See eg. TS 27.005 4.3 // Commands like AT+CMGS have a "> " prompt writeCtrlZ(s_smsPDU); s_smsPDU = NULL; } else switch (s_type) { case NO_RESULT: handleUnsolicited(line); break; case NUMERIC: if (sp_response->p_intermediates == NULL && isdigit(line[0]) ) { addIntermediate(line); } else { /* either we already have an intermediate response or the line doesn't begin with a digit */ handleUnsolicited(line); } break; case SINGLELINE: if (sp_response->p_intermediates == NULL && strStartsWith (line, s_responsePrefix) ) { addIntermediate(line); } else { /* we already have an intermediate response */ handleUnsolicited(line); } break; case MULTILINE: if (strStartsWith (line, s_responsePrefix)) { addIntermediate(line); } else { handleUnsolicited(line); } break; default: /* this should never be reached */ RLOGE("Unsupported AT command type %d\n", s_type); handleUnsolicited(line); break; } pthread_mutex_unlock(&s_commandmutex);}
hardware\ril\reference-ril\atchannel.c
回调处理
static void handleUnsolicited(const char *line){ if (s_unsolHandler != NULL) { s_unsolHandler(line, NULL); }}
hardware\ril\reference-ril\reference-ril.c
处理来自基带处理器的AT指令
static void onUnsolicited (const char *s, const char *sms_pdu){ char *line = NULL, *p; int err; /* Ignore unsolicited responses until we're initialized. * This is OK because the RIL library will poll for initial state */ if (sState == RADIO_STATE_UNAVAILABLE) { return; } if (strStartsWith(s, "%CTZV:")) { /* TI specific -- NITZ time */ char *response; line = p = strdup(s); at_tok_start(&p); err = at_tok_nextstr(&p, &response); if (err != 0) { RLOGE("invalid NITZ line %s\n", s); } else { RIL_onUnsolicitedResponse ( RIL_UNSOL_NITZ_TIME_RECEIVED, response, strlen(response)); } free(line); } else if (strStartsWith(s,"+CRING:") || strStartsWith(s,"RING") || strStartsWith(s,"NO CARRIER") || strStartsWith(s,"+CCWA") ) { RIL_onUnsolicitedResponse ( RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, NULL, 0);#ifdef WORKAROUND_FAKE_CGEV RIL_requestTimedCallback (onDataCallListChanged, NULL, NULL); //TODO use new function#endif /* WORKAROUND_FAKE_CGEV */ } else if (strStartsWith(s,"+CREG:") || strStartsWith(s,"+CGREG:") ) { RIL_onUnsolicitedResponse ( RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, NULL, 0);#ifdef WORKAROUND_FAKE_CGEV RIL_requestTimedCallback (onDataCallListChanged, NULL, NULL);#endif /* WORKAROUND_FAKE_CGEV */ } else if (strStartsWith(s, "+CMT:")) { RIL_onUnsolicitedResponse ( RIL_UNSOL_RESPONSE_NEW_SMS, sms_pdu, strlen(sms_pdu)); } else if (strStartsWith(s, "+CDS:")) { RIL_onUnsolicitedResponse ( RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT, sms_pdu, strlen(sms_pdu)); } else if (strStartsWith(s, "+CGEV:")) { /* Really, we can ignore NW CLASS and ME CLASS events here, * but right now we don't since extranous * RIL_UNSOL_DATA_CALL_LIST_CHANGED calls are tolerated */ /* can't issue AT commands here -- call on main thread */ RIL_requestTimedCallback (onDataCallListChanged, NULL, NULL);#ifdef WORKAROUND_FAKE_CGEV } else if (strStartsWith(s, "+CME ERROR: 150")) { RIL_requestTimedCallback (onDataCallListChanged, NULL, NULL);#endif /* WORKAROUND_FAKE_CGEV */ } else if (strStartsWith(s, "+CTEC: ")) { int tech, mask; switch (parse_technology_response(s, &tech, NULL)) { case -1: // no argument could be parsed. RLOGE("invalid CTEC line %s\n", s); break; case 1: // current mode correctly parsed case 0: // preferred mode correctly parsed mask = 1 << tech; if (mask != MDM_GSM && mask != MDM_CDMA && mask != MDM_WCDMA && mask != MDM_LTE) { RLOGE("Unknown technology %d\n", tech); } else { setRadioTechnology(sMdmInfo, tech); } break; } } else if (strStartsWith(s, "+CCSS: ")) { int source = 0; line = p = strdup(s); if (!line) { RLOGE("+CCSS: Unable to allocate memory"); return; } if (at_tok_start(&p) < 0) { free(line); return; } if (at_tok_nextint(&p, &source) < 0) { RLOGE("invalid +CCSS response: %s", line); free(line); return; } SSOURCE(sMdmInfo) = source; RIL_onUnsolicitedResponse(RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, &source, sizeof(source)); } ......}
hardware\ril\libril\ril.cpp
响应请求
#if defined(ANDROID_MULTI_SIM)extern "C"void RIL_onUnsolicitedResponse(int unsolResponse, const void *data, size_t datalen, RIL_SOCKET_ID socket_id)#elseextern "C"void RIL_onUnsolicitedResponse(int unsolResponse, const void *data, size_t datalen)#endif{ int unsolResponseIndex; unsolResponseIndex = unsolResponse - RIL_UNSOL_RESPONSE_BASE; // Grab a wake lock if needed for this reponse, // as we exit we'll either release it immediately // or set a timer to release it later. switch (s_unsolResponses[unsolResponseIndex].wakeType) { case WAKE_PARTIAL: grabPartialWakeLock(); shouldScheduleTimeout = true; break; case DONT_WAKE: default: // No wake lock is grabed so don't set timeout shouldScheduleTimeout = false; break; } appendPrintBuf("[UNSL]< %s", requestToString(unsolResponse)); int responseType; if (s_callbacks.version >= 13 && s_unsolResponses[unsolResponseIndex].wakeType == WAKE_PARTIAL) { responseType = RESPONSE_UNSOLICITED_ACK_EXP; } else { responseType = RESPONSE_UNSOLICITED; } pthread_rwlock_t *radioServiceRwlockPtr = radio::getRadioServiceRwlock((int) soc_id); int rwlockRet = pthread_rwlock_rdlock(radioServiceRwlockPtr); assert(rwlockRet == 0); //执行响应函数 ret = s_unsolResponses[unsolResponseIndex].responseFunction( (int) soc_id, responseType, 0, RIL_E_SUCCESS, const_cast<void*>(data), datalen); ......}
hardware\ril\libril\ril.cpp
static UnsolResponseInfo s_unsolResponses[] = {#include "ril_unsol_commands.h"};
hardware\ril\libril\ril_unsol_commands.h
函数表,由responseFunction查表调用
{RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, radio::radioStateChangedInd, WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, radio::callStateChangedInd, WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, radio::networkStateChangedInd, WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_NEW_SMS, radio::newSmsInd, WAKE_PARTIAL}, ......
5. 经请求的AT通讯
hardware\ril\libril\ril.cpp
注册回调函数
extern "C" voidRIL_register (const RIL_RadioFunctions *callbacks) { int ret; int flags; //内存分配 memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions)); for (int i = 0; i < (int)NUM_ELEMS(s_commands); i++) { assert(i == s_commands[i].requestNumber); //断言 } for (int i = 0; i < (int)NUM_ELEMS(s_unsolResponses); i++) { assert(i + RIL_UNSOL_RESPONSE_BASE == s_unsolResponses[i].requestNumber); } //注册 radio::registerService(&s_callbacks, s_commands);}
hardware\ril\libril\ril_service.cpp
回调函数和函数路由表
void radio::registerService(RIL_RadioFunctions *callbacks, CommandInfo *commands) { using namespace android::hardware; int simCount = 1; const char *serviceNames[] = { android::RIL_getServiceName() #if (SIM_COUNT >= 2) , RIL2_SERVICE_NAME #if (SIM_COUNT >= 3) , RIL3_SERVICE_NAME #if (SIM_COUNT >= 4) , RIL4_SERVICE_NAME #endif #endif #endif }; #if (SIM_COUNT >= 2) simCount = SIM_COUNT; #endif configureRpcThreadpool(1, true /* callerWillJoin */); //RPC线程池 for (int i = 0; i < simCount; i++) { //一张sim卡对应一项服务 pthread_rwlock_t *radioServiceRwlockPtr = getRadioServiceRwlock(i); int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr); assert(ret == 0); radioService[i] = new RadioImpl; radioService[i]->mSlotId = i; oemHookService[i] = new OemHookImpl; oemHookService[i]->mSlotId = i; RLOGD("registerService: starting IRadio %s", serviceNames[i]); android::status_t status = radioService[i]->registerAsService(serviceNames[i]); status = oemHookService[i]->registerAsService(serviceNames[i]); ret = pthread_rwlock_unlock(radioServiceRwlockPtr); assert(ret == 0); } s_vendorFunctions = callbacks; s_commands = commands;}
hardware\ril\libril\ril_service.cpp
s_vendorFunctions为上述RIL_RadioFunctions
#if defined(ANDROID_MULTI_SIM)#define CALL_ONREQUEST(a, b, c, d, e) \ s_vendorFunctions->onRequest((a), (b), (c), (d), ((RIL_SOCKET_ID)(e)))#define CALL_ONSTATEREQUEST(a) s_vendorFunctions->onStateRequest((RIL_SOCKET_ID)(a))#else#define CALL_ONREQUEST(a, b, c, d, e) s_vendorFunctions->onRequest((a), (b), (c), (d))#define CALL_ONSTATEREQUEST(a) s_vendorFunctions->onStateRequest()#endif
hardware\ril\libril\ril_service.cpp
bool dispatchVoid(int serial, int slotId, int request) { RequestInfo *pRI = android::addRequestToList(serial, slotId, request); if (pRI == NULL) { return false; } CALL_ONREQUEST(request, NULL, 0, pRI, slotId); return true;}
hardware\ril\reference-ril\reference-ril.c
宏调用
static voidonRequest (int request, void *data, size_t datalen, RIL_Token t){ ATResponse *p_response; int err; ...... switch (request) { ...... case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND: // 3GPP 22.030 6.5.5 // "Releases all held calls or sets User Determined User Busy // (UDUB) for a waiting call." at_send_command("AT+CHLD=0", NULL); //发送AT指令 default: RLOGD("Request not supported. Tech: %d",TECH(sMdmInfo)); RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0); //处理请求 break; }}
hardware\ril\libril\ril.cpp
同上未经请求的回复,对应一张函数表,AT命令的请求相应均由函数跳转表路由到hardware\ril\libril\ril_service.cpp 去执行
static CommandInfo s_commands[] = {#include "ril_commands.h"};
6. RIL抽象服务
hardware\ril\libril\ril_service.cpp
Return<void> RadioImpl::getModemActivityInfo(int32_t serial) { //查询活跃信息#if VDBG RLOGD("getModemActivityInfo: serial %d", serial);#endif dispatchVoid(serial, mSlotId, RIL_REQUEST_GET_ACTIVITY_INFO); //上述请求派发函数 return Void();}
hardware\ril\libril\ril_service.cpp
struct RadioImpl : public IRadio { int32_t mSlotId; sp<IRadioResponse> mRadioResponse; sp<IRadioIndication> mRadioIndication; Return<void> setResponseFunctions( const ::android::sp<IRadioResponse>& radioResponse, const ::android::sp<IRadioIndication>& radioIndication);}
intermediates\hardware\interfaces\radio\1.0\[email protected]_genc++_headers\gen\android\hardware\radio\1.0\IRadio.h
一项HAL的Binder服务接口;在hardware\interfaces\radio\1.0\vts\functional提供了一些接口和测试案例
struct IRadio : public ::android::hidl::base::V1_0::IBase { virtual bool isRemote() const override { return false; } ...... }
暂且分析到这,以后在扩展到上层系统框架。
更多相关文章
- android LOG机制
- android的wake_lock介绍
- android 编译系统阅读与分析(1)-envsetup.sh (editing....)
- Android中 Js 扩展及交互
- Android(安卓)M App 永久隐藏导航栏的Solution
- Android(安卓)SurfaceFlinger VSync流程分析
- SEAndroid安全机制对Android属性访问的保护分析
- Android(安卓)GPIO LED 驱动与HAL分析
- Rexsee API介绍:Android传感器系列之 - 磁场传感器Magnetic Field