天天看點

Maemo Linux手機平台系列分析:7 Maemo平台開發之LibOSSO

  這部分的内容:

  • LibOSSO介紹
  • 調用LibOSSO去實作D-Bus方法調用
  • 調用LibOSSO實作異步方法調用
  • 裝置狀态和模式的通知
  • 模拟裝置狀态的改變

  LibOSSO 介紹 幾乎 Maemo 中所有的程式都要使用 LibOSSO 庫。主要的原因是: LibOSSO 可以保護一些将被殺掉的程序幸免于難,而不被殺掉。當從任務導航欄中啟動一個沒有注冊 D-Bus 服務的程式時,超過一定時間,這個程式将由桌面環境去殺掉。 LibOSSO 另外一個方面的功能是版本隔離作用:由于 D-Bus 仍然在開發更新中,由 LibOSSO 對 D-Bus 進行封裝,使用者程式隻要調用 LibOSSO 的對應接口就行,不必去直接調用 D-Bus 接口。 除了保護和隔離作用外, LibOSSO 還提供了另外一些有用的功能:自動儲存和狀态儲存;處理硬體狀态;以及其它重要的事件。還有:對 D-Bus 的封裝可以使得 LibOSSO 提供非常友善的函數去做 RPC( 遠端調用 ). 下面我們會重點介紹 LibOSSO 的 RPC :   通過 LibOSSO 庫實作 D-Bus 的方法調用 我們從哪開始呢?記得我們前面曾直接使用 libdbus 的函數實作了一個 RPC, 這裡我們依然使用這個例子,隻不過,這次我們用 LibOSSO 的函數去實作一個同樣彈出對話框的功能。當然, LibOSSO 庫中有個函數 ( osso_system_note_dialog ) 可以直接去實作這種功能 . 我們暫時不用它。我們通過詳細的代碼看看 LibOSSO 如何支援 RPC 的。 頭檔案 : #include <libosso.h> //必須包含這個頭檔案     //與上面一個例子同樣的定義 #define SYSNOTE_NAME "org.freedesktop.Notifications" #define SYSNOTE_OPATH "/org/freedesktop/Notifications" #define SYSNOTE_IFACE "org.freedesktop.Notifications" #define SYSNOTE_NOTE "SystemNoteDialog" [ 使用 LibOSSO 庫函數需要包含的頭檔案 ] 使用 LibOSSO 庫,僅僅需要包含一個 libosso.h 頭檔案,我們這裡同樣定義 D-Bus service 名字,對象路徑、接口名字、方法名字; 我們看看 LibOSSO 的上下文如何建立的以及如何運作的: int main(int argc, char** argv) {    

 osso_context_t* ossoContext = NULL;     g_print ( "Initializing LibOSSO /n " );  

 ossoContext = osso_initialize(ProgName, "1.0", FALSE, NULL);   if (ossoContext == NULL) {     g_error("Failed to initialize LibOSSO/n");  }    g_print("Invoking the method call/n");  runRPC(ossoContext);    g_print("Shutting down LibOSSO/n");  

 osso_deinitialize(ossoContext);  ossoContext = NULL;     g_print ( "Quitting /n " );  return EXIT_SUCCESS; } [LibOSSO 初始化與解除安裝,見 (libosso-example-sync/libosso-rpc-sync.c) ] LibOSSO 上下文,這個資料結構含有通過 D-Bus 進行通信的必要資訊。當準備建立這個上下文時,第一個參數:你需要傳入一個程式名字給 osso_initialize, 這個名字有兩個用途: 1 ,注冊給 D-Bus; 2, 将來阻止銷毀任務殺掉你的程序。如果這個名字沒有包含任何點字元,那麼, LibOSSO 會自動把那個名字加個字首: com.nokia. 一般情況下,使用者是看不到這個名字的,不過,這問題不大。如果你老是使用這種沒有字首的名字的話,可能會和别人的程式名沖突。建議使用全名。 第二個參數:版本号,目前 LibOSSO 中并沒有用,不過你可以加上,以後可能會用的。 倒數第二個參數:如果最後一個參數傳入 NULL ,這個參數不會起到任何作用。最後一個參數:使用 LibOSSO 自己的 GMainLoop 還是外面的,如果傳入 NULL ,則直接使用 LibOSSO 内部的主循環。 釋放 LibOSSO 上下文會自動關閉連結到 D-Bus 上面的連結通道,同時釋放一些配置設定的記憶體。 下面這一段代碼示範了如何使用 LibOSSO 做 RPC 調用,以及錯誤處理。 static void runRPC(osso_context_t* ctx) {      const char* dispMsg = "Hello SystemNote!/nVia LibOSSO/sync.";    gint iconType = OSSO_GN_ERROR;    const char* labelText = "";      osso_return_t result;    osso_rpc_t methodResult = {};    g_print("runRPC called/n");    g_assert(ctx != NULL);    

 result = osso_rpc_run(ctx,                         SYSNOTE_NAME,                              SYSNOTE_OPATH,                                 SYSNOTE_IFACE,                                   SYSNOTE_NOTE,                                   &methodResult,                        

                        DBUS_TYPE_STRING, dispMsg,                         DBUS_TYPE_UINT32, iconType,                         DBUS_TYPE_STRING, labelText,                         DBUS_TYPE_INVALID);    if (result != OSSO_OK) {     g_error("Error launching the RPC (%s)/n",             ossoErrorStr(result));      }  g_print("RPC launched successfully/n");    

 g_print("Method returns: ");  printOssoValue(&methodResult);  g_print("/n");    g_print("runRPC ending/n"); } 大家一定要注意:函數 osso_rpc_run 是個同步(阻塞)調用,有兩種結果:有個及時的相應;或者逾時傳回。在實際使用過程中,一定要銘記這是一個阻塞的調用。另外呢: LibOSSO 也有一套異步(非阻塞)的 RPC 函數。 如果你的方法調用傳回不止一個值 (D-Bus 是支援的 ) ,不過, LibOSSO 目前并不支援傳回所有的(而是傳回第一個)。 對于函數傳回碼的 Error code ,這裡非常直接的。我有篇文章是專門講如何做 Error code, 那篇文章中對于 Error code 的解析非常好:有巧妙的、有直白的,也有更專業的。 static const gchar* ossoErrorStr(osso_return_t errCode) {    switch (errCode) {     case OSSO_OK:       return "No error (OSSO_OK)";     case OSSO_ERROR:       return "Some kind of error occurred (OSSO_ERROR)";     case OSSO_INVALID:       return "At least one parameter is invalid (OSSO_INVALID)";     case OSSO_RPC_ERROR:       return "Osso RPC method returned an error (OSSO_RPC_ERROR)";     case OSSO_ERROR_NAME:       return "(undocumented error) (OSSO_ERROR_NAME)";     case OSSO_ERROR_NO_STATE:       return "No state file found to read (OSSO_ERROR_NO_STATE)";     case OSSO_ERROR_STATE_SIZE:       return "Size of state file unexpected (OSSO_ERROR_STATE_SIZE)";     default:       return "Unknown/Undefined";  } } 對于函數傳回值的解析,有點複雜,因為傳回值是包含在一個結構中的,需要逐個分揀。 static void printOssoValue(const osso_rpc_t* val) {    g_assert(val != NULL);    // 對傳回值的類型進行分揀,還是D-Bus的特色,讓人用起來不習慣  switch (val->type) {     case DBUS_TYPE_BOOLEAN:       g_print("boolean:%s", (val->value.b == TRUE)?"TRUE":"FALSE");       break;     case DBUS_TYPE_DOUBLE:       g_print("double:%.3f", val->value.d);       break;     case DBUS_TYPE_INT32:       g_print("int32:%d", val->value.i);       break;     case DBUS_TYPE_UINT32:       g_print("uint32:%u", val->value.u);       break;     case DBUS_TYPE_STRING:       g_print("string:'%s'", val->value.s);       break;     case DBUS_TYPE_INVALID:       g_print("invalid/void");       break;     default:       g_print("unknown(type=%d)", val->type);       break;  } } 請注意: libOSSO RPC 函數并不支援數組參數,是以你要使用一些簡單的參數了 L 。這可是不好的消息, GArray 不能在 libOSSO 中使用了? D-Bus 可是支援的啊。估計是支援的,有時間試探試探。 把上面的例子編譯後運作,我們發現會彈出一個熟悉的對話框。 [sbox-CHINOOK_X86: ~/libosso-example-sync] > run-standalone.sh ./libosso-rpc-sync Initializing LibOSSO Invoking the method call runRPC called /dev/dsp: No such file or directory RPC launched successfully Method returns: uint32:8 runRPC ending Shutting down LibOSSO Quitting 和前一個例子的差別就是,多了一個關于音頻裝置的錯誤資訊。這個資訊可能會在 RPC 傳回之前出現,因為 runRPC 是阻塞調用。

Maemo Linux手機平台系列分析:7 Maemo平台開發之LibOSSO

  我們在前面的代碼中用到了一個程式名稱: ProgName ,我們一般把它定義在 makefile 檔案中,為什麼呢?有時候我們需要分别編譯 i386 和 ARMEL ,如果定義在頭檔案中,可能需要區分定義,如果放在 makefile 總定義,由于 makefile 的開頭一般會先區分 i386, 和 ARM 。 另外一個好處是,我們把 ProgName 定義在 makefile 中,可以提高上面代碼的複用率。 # define a list of pkg-config packages we want to use pkg_packages := glib-2.0 libosso   # ... Listing cut for brevity ...   libosso-rpc-sync: libosso-rpc-sync.c       $(CC) $(CFLAGS) -DProgName=/"LibOSSOExample/" / #定義在這裡好處有兩個,1 提高代碼重用性,2 友善為不同的archetecture定義        $< -o [email protected] $(LDFLAGS)   LibOSSO 的異步調用 有時候,一個函數需要很長時間才能執行完,或者你不能确定這個函數是否需要很長時間,這些情況下,你應當使用異步調用,而不要使用同步調用。同步和異步最大的差別就是:異步是把同步割成兩部分,啟動 RPC 和在回調中處理傳回結果。就是說,你 RPC 什麼時候回來,我什麼時候處理你的結果,這樣就不至于導緻 D-Bus 的逾時問題。 為了使用 LibOSSO 的回調和控制其主循環,我們需要建立一個狀态變量,這個狀态變量在需要時,會傳遞給回調。 typedef struct {    GMainLoop* mainloop;     osso_context_t* ossoContext; } ApplicationState; 函數 osso_rpc_async_run 是用來啟動異步調用的,并且它立即傳回。如果它傳回了一個錯誤,可能是 client 端的錯誤,而不是 server 端得錯誤,因為 RPC 的錯誤沒有通過它傳回。那麼,誰來處理 RPC 的回應資料呢,回調函數! static gboolean launchRPC(gpointer userData) {    ApplicationState* state = (ApplicationState*)userData;    const char* dispMsg = "Hello SystemNote!/nVia LibOSSO/async.";    gint iconType = OSSO_GN_ERROR;    const char* labelText = "Execute!";      osso_return_t result;    g_print("launchRPC called/n");    g_assert(state != NULL);        

 result = osso_rpc_async_run(state->ossoContext,                               SYSNOTE_NAME,                                   SYSNOTE_OPATH,                                      SYSNOTE_IFACE,                                        SYSNOTE_NOTE,                                       rpcCompletedCallback,                                  state,                                                                       DBUS_TYPE_STRING, dispMsg,                               DBUS_TYPE_UINT32, iconType,                               DBUS_TYPE_STRING, labelText,                               DBUS_TYPE_INVALID);  

 if (result != OSSO_OK) {     g_error("Error launching the RPC (%s)/n",             ossoErrorStr(result));      }  g_print("RPC launched successfully/n");    g_print("launchRPC ending/n");    

 return FALSE; } 由一個簡單的回調函數來處理 RPC 的傳回,在這個函數中使用 osso_rpc_async_run 的兩個參數: interface 和 method ,這兩個參數非常有用,這樣的話,你就可以對多個 RPC 使用同一個回調函數了,因為這些 RPC 的 interface 和 method 都不同。 這個傳回值的資料結構的記憶體是由 LibOSSO 配置設定的,當你的回調函數傳回後,這個變量會由 LibOSSO 釋放,是以,你不必手動去處理這個事情(這也是使用回調機制要注意的問題)。 static void rpcCompletedCallback(const gchar* interface,                                  const gchar* method,                                  osso_rpc_t* retVal,                                  gpointer userData) {    ApplicationState* state = (ApplicationState*)userData;    g_print("rpcCompletedCallback called/n");    g_assert(interface != NULL);  g_assert(method != NULL);  g_assert(retVal != NULL);  g_assert(state != NULL);    g_print(" interface: %s/n", interface);  g_print(" method: %s/n", method);  

 g_print(" result: ");  printOssoValue(retVal);  g_print("/n");      g_main_loop_quit(state->mainloop);    g_print("rpcCompletedCallback done/n"); }   下面是例子的代碼 : int main(int argc, char** argv) {      ApplicationState state = {};    osso_return_t result;    gint rpcTimeout;    g_print("Initializing LibOSSO/n");   state.ossoContext = osso_initialize(ProgName, "1.0", FALSE, NULL);   if (state.ossoContext == NULL) {     g_error("Failed to initialize LibOSSO/n");  }    

 result = osso_rpc_get_timeout(state.ossoContext, &rpcTimeout);  if (result != OSSO_OK) {     g_error("Error getting default RPC timeout (%s)/n",             ossoErrorStr(result));  }  

 g_print("Default RPC timeout is %d (units)/n", rpcTimeout);    g_print("Creating a mainloop object/n");  

 state.mainloop = g_main_loop_new(NULL, FALSE);  if (state.mainloop == NULL) {     g_error("Failed to create a GMainLoop/n");  }    g_print("Adding timeout to launch the RPC in one second/n");  

 g_timeout_add(1000, (GSourceFunc)launchRPC, &state);    g_print("Starting mainloop processing/n");  g_main_loop_run(state.mainloop);    g_print("Out of mainloop, shutting down LibOSSO/n");    osso_deinitialize(state.ossoContext);   state.ossoContext = NULL;      g_main_loop_unref(state.mainloop);  state.mainloop = NULL;    g_print("Quitting/n");  return EXIT_SUCCESS; } 測試的結果可能有點意外 : [sbox-CHINOOK_X86: ~/libosso-example-async] > run-standalone.sh ./libosso-rpc-async Initializing LibOSSO Default RPC timeout is -1 (units) Creating a mainloop object Adding timeout to launch the RPC in one second Starting mainloop processing launchRPC called RPC launched successfully launchRPC ending rpcCompletedCallback called  interface: org.freedesktop.Notifications  method: SystemNoteDialog  result: uint32:10 rpcCompletedCallback done Out of mainloop, shutting down LibOSSO Quitting /dev/dsp: No such file or directory 你可能已經注意到了,聲音裝置的錯誤,在最下面才列印出來,為什麼?看起來好像是對話框運作很久後,才開始播放聲音。這和前面的同步調用有明顯的差別。這再次說明了:你不能依賴 D-Bus 的遠端方法調用的時間精确性;

Maemo Linux手機平台系列分析:7 Maemo平台開發之LibOSSO

  裝置狀态與模式的通知: 既然 Internet Tablets 是移動裝置,你當然希望人們在移動過程中使用它(或者您開發的軟體)。另外,在飛機上或者其他地方可能無法連接配接網絡。是以你需要知道如何處理這種狀态切換 為了示範如何處理裝置的重要狀态,下面我們用一個小工具程式示範一下。比如說:飛行模式。使用者切換裝置到“掉線”模式,就會激活飛行模式。 這個程式是用來跟蹤手機的背光情況,通過定期的向系統輪詢,是否推遲關閉背光。一般情況下,螢幕如果一段時間沒有使用,則背光關閉,這主要為了省電,對于待機時間來說是非常重要的。當然,使用者可以通過手機設定來完成背光的配置,不過我們不這麼做。我們用這個程式來自動推遲背光的關閉 . 這裡我們使用 45 秒的定時器來控制。 我們同時也跟蹤手機的模式,一旦手機進入了飛行模式,這個程式将會被關閉,是以,如果手機已經處于飛行模式,是不能啟動這個程式的。 由于這個程式是在背景運作的,沒有自己的使用者界面,是以我們使用通知對話框來顯示一些資訊給使用者,提醒使用者。 下面是主要的流程 : int main(int argc, char** argv) {    

 ApplicationState state = {};    g_print(PROGNAME ":main Starting/n");  

 if (setupAppState(&state) == FALSE) {     g_print(PROGNAME ":main Setup failed, doing cleanup/n");     releaseAppState(&state);     g_print(PROGNAME ":main Terminating with failure/n");     return EXIT_FAILURE;  }    g_print(PROGNAME ":main Starting mainloop processing/n");  g_main_loop_run(state.mainloop);  g_print(PROGNAME ":main Out of main loop (shutting down)/n");  

 displayExitMessage(&state, ProgName " exiting");      releaseAppState(&state);    g_print(PROGNAME ":main Quitting/n");  return EXIT_SUCCESS; } 為了讓裝置狀态的回調函數能強制讓程式退出,我們需要傳遞 LibOSSO 的上下文給這個回調函數,同時也要讓回調函數能通路主循環對象以及用一個标記量來通知定時器去退出(因為定時器是不能在 Glib 外部被删除的)。 typedef struct {    GMainLoop* mainloop;    osso_context_t* ossoContext;  

 gboolean running; } ApplicationState; 所有的設定和啟動操作都是在函數 setupAppState 中完成的 , 同時也包含了一些非常重要的步驟: static gboolean setupAppState(ApplicationState* state) {    osso_return_t result;    g_assert(state != NULL);    g_print(PROGNAME ":setupAppState starting/n");    

 memset(state, 0, sizeof(ApplicationState));    g_print(PROGNAME ":setupAppState Initializing LibOSSO/n");        state->ossoContext = osso_initialize(ProgName, "1.0", FALSE, NULL);  if (state->ossoContext == NULL) {     g_printerr(PROGNAME ": Failed to initialize LibOSSO/n");     return FALSE;  }    g_print(PROGNAME ":setupAppState Creating a GMainLoop object/n");  

 state->mainloop = g_main_loop_new(NULL, FALSE);  if (state->mainloop == NULL) {     g_printerr(PROGNAME ": Failed to create a GMainLoop/n");     return FALSE;  }    g_print(PROGNAME           ":setupAddState Adding hw-state change callback./n");  

 state->running = TRUE;  

 result = osso_hw_set_event_cb(state->ossoContext,                                 NULL,                                 deviceStateChanged,                                 state);  if (result != OSSO_OK) {     g_printerr(PROGNAME                ":setupAppState Failed to get state change CB/n");    

    return FALSE;  }    if (state->running == FALSE) {     g_print(PROGNAME ":setupAppState In offline, not continuing./n");     displayExitMessage(state, ProgName " not available in Offline mode");     return FALSE;  }    g_print(PROGNAME ":setupAppState Adding blanking delay timer./n");  if (g_timeout_add(45000,                     (GSourceFunc)delayBlankingCallback,                     state) == 0) {    

    g_printerr(PROGNAME ": Failed to create a new timer callback/n");     return FALSE;  }      g_print(PROGNAME ":setupAppState Unblanking the display/n");  result = osso_display_state_on(state->ossoContext);  if (result != OSSO_OK) {     g_printerr(PROGNAME ": Failed in osso_display_state_on (%s)/n",                ossoErrorStr(result));    

    return FALSE;  }    

       g_print(PROGNAME ":setupAppState Displaying Note dialog/n");  result = osso_system_note_dialog(state->ossoContext,                                                                       "Started " ProgName "./n"                        "Please remember to stop it when you're done, "                                 "in order to conserve battery power.",                                                                       OSSO_GN_WARNING,                                   

                                   NULL);  if (result != OSSO_OK) {     g_error(PROGNAME ": Error displaying Note dialog (%s)/n",             ossoErrorStr(result));  }  g_print(PROGNAME ":setupAppState Requested for the dialog/n");    

 delayDisplayBlanking(state);    g_print(PROGNAME ":setupAppState Completed/n");      return TRUE; }   處理裝置狀态改變的回調是有函數 osso_hw_set_event_cb 注冊的,同時我們也知道了如何強制打開背光。與此同時,我們注冊了定時器的回調,這個回調在 45 秒後被調用。 處理裝置狀态的回調将接收新的“硬體狀态”和使用者傳入的自定義資料。 在注冊時,它就被調用了一次。這次調用講通知應用程式目前裝置的最初狀态。我們就利用這個來判斷目前手機是否處于飛行狀态。另外,由于我們不知道這個主循環是否處于激活狀态,是以我們使用了一個額外的标志量來通知定時器回調函數去關閉這個主循環。 static void deviceStateChanged(osso_hw_state_t* hwState,                                gpointer data) {  ApplicationState* state = (ApplicationState*)data;    g_print(PROGNAME ":deviceStateChanged Starting/n");    printDeviceState(hwState);    

 if (hwState->sig_device_mode_ind == OSSO_DEVMODE_FLIGHT) {     g_print(PROGNAME ":deviceStateChanged In/going into offline./n");      

    g_main_loop_quit(state->mainloop);    

    state->running = FALSE;  } } 函數 printDeviceState 主要是把手機的狀态給列印出來。 #define BOOLSTR(p) ((p)?"YES":"no")   static void printDeviceState(osso_hw_state_t* hwState) {    gchar* modeStr = "Unknown";    g_assert(hwState != NULL);    switch(hwState->sig_device_mode_ind) {     case OSSO_DEVMODE_NORMAL:             modeStr = "Normal";       break;     case OSSO_DEVMODE_FLIGHT:             modeStr = "Flight";       break;     case OSSO_DEVMODE_OFFLINE:      

      modeStr = "Offline";       break;     case OSSO_DEVMODE_INVALID:             modeStr = "Invalid(?)";       break;     default:             break;  }  g_print(     "Mode: %s, Shutdown: %s, Save: %s, MemLow: %s, RedAct: %s/n",     modeStr,         BOOLSTR(hwState->shutdown_ind),    

    BOOLSTR(hwState->save_unsaved_data_ind),    

    BOOLSTR(hwState->memory_low_ind),         BOOLSTR(hwState->system_inactivity_ind)); }   如何延遲關閉螢幕呢?調用 LibOSSO 的一個函數即可以完成這個功能: ( osso_display_blanking_pause ) ,這個函數在很多地方被用到。 static void delayDisplayBlanking(ApplicationState* state) {    osso_return_t result;    g_assert(state != NULL);    result = osso_display_blanking_pause(state->ossoContext);  if (result != OSSO_OK) {     g_printerr(PROGNAME ":delayDisplayBlanking. Failed (%s)/n",                ossoErrorStr(result));      } else {     g_print(PROGNAME ":delayDisplayBlanking RPC succeeded/n");  } } 定時器回調一般情況下用作,告訴系統推遲關屏,同時也可以檢查該程式釋放已經關閉。如果程式已經關閉,在定時器銷毀自己(通過傳回 FALSE ) : static gboolean delayBlankingCallback(gpointer data) {  ApplicationState* state = (ApplicationState*)data;    g_print(PROGNAME ":delayBlankingCallback Starting/n");    g_assert(state != NULL);  

 if (state->running == FALSE) {     g_print(PROGNAME ":delayBlankingCallback Removing/n");     return FALSE;  }      delayDisplayBlanking(state);    g_print(PROGNAME ":delayBlankingCallback Done/n");    

 return TRUE; }   做些釋放工作: static void releaseAppState(ApplicationState* state) {    g_print(PROGNAME ":releaseAppState starting/n");    g_assert(state != NULL);    

 state->running = FALSE;    

 if (state->ossoContext != NULL) {     osso_hw_unset_event_cb(state->ossoContext, NULL);  }      if (state->mainloop != NULL) {     g_print(PROGNAME ":releaseAppState Releasing mainloop object./n");     g_main_loop_unref(state->mainloop);     state->mainloop = NULL;  }      if (state->ossoContext != NULL) {     g_print(PROGNAME ":releaseAppState De-init LibOSSO./n");     osso_deinitialize ( state->ossoContext);     state->ossoContext = NULL;   }   }   編譯運作的結果 : [sbox-CHINOOK_X86: ~/libosso-flashlight] > run-standalone.sh ./flashlight flashlight:main Starting flashlight:setupAppState starting flashlight:setupAppState Initializing LibOSSO flashlight:setupAppState Creating a GMainLoop object flashlight:setupAddState Adding hw-state change callback. flashlight:deviceStateChanged Starting Mode: Normal, Shutdown: no, Save: no, MemLow: no, RedAct: no flashlight:setupAppState Adding blanking delay timer. flashlight:setupAppState Unblanking the display flashlight:setupAppState Displaying Note dialog flashlight:setupAppState Requested for the dialog flashlight:delayDisplayBlanking RPC succeeded flashlight:setupAppState Completed flashlight:main Starting mainloop processing /dev/dsp: No such file or directory flashlight:delayBlankingCallback Starting flashlight:delayDisplayBlanking RPC succeeded flashlight:delayBlankingCallback Done ... 實際的運作結果:

Maemo Linux手機平台系列分析:7 Maemo平台開發之LibOSSO

繼續閱讀