[WM]就是那道光,我要自己控制我的背光

在WM裝置中,大多數的電力來源都是電池,所以一些省電的動作也會產生,例如說多久沒有操作裝置的話,系統會關閉LCD的背光以節省電力,使用AC電源的時候,LCD的背光應該多亮,使用電池的時候背光應該多亮等等

在WM裝置中,大多數的電力來源都是電池,所以一些省電的動作也會產生,例如說多久沒有操作裝置的話,系統會關閉LCD的背光以節省電力,使用AC電源的時候,LCD的背光應該多亮,使用電池的時候背光應該多亮等等。 那麼相關的需求就來了,一些比較常見的狀況(需求)下約是下面這些
  • 我的程式要一直跑,無論如何我不想讓系統進入待機的狀態,可不可以?
  • 一段時間沒有動作的話,系統會自動關閉背光,但是我在放電影啊,拜託不要關
  • 能不能讓我的程式去控制關閉背光的時間?
  • 能不能讓我控制背光的級距(Level)?
今天就針對這幾個部分來做測試,首先是怎麼不讓系統進入待機呢?為什麼不要進入待機的狀態,最明顯的例子是導航系統,如果導航系統導到一半,那不就.. 要做到這個功能不困難,只要呼叫一個SystemIdleTimerReset就行了,宣告的方式如下

     _
    Public Sub SystemIdleTimerReset()
    End Sub
使用的時候也是直接呼叫就可以了

這樣就可以讓系統不進入待機了;這個時候程式人的潔癖又來了,那我要多久呼叫一次SystemIdleTimerReset?每秒?每分鐘?
那麼就要知道多久會進入待機了,這個值也是放在登錄檔中,利用登錄編輯程式(remote tool),可以在這個位置找到
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Power\BattPowerOff

知道放置的位置之後,就可以利用程式把這個值給讀出來了,像是下面的方式

    ''' 取得系統進入待機前等待的秒數
    ''' reference http://support.microsoft.com/default.aspx/kb/180898
    ''' 
    ''' 
    ''' 
    Private Function GetSuspendTimeOutSetting() As Integer
        Dim hHKLM As Microsoft.Win32.RegistryKey
        hHKLM = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("System\CurrentControlSet\Control\Power", False)
        Dim intSuspendTimeout As Integer = CInt(hHKLM.GetValue("BattPowerOff", -1))
        hHKLM.Close()

        If intSuspendTimeout <> -1 Then
            ''成功取得reg內容
            Return intSuspendTimeout
        Else
            Return -1
        End If
    End Function
於是乎我們就搞定了第一個需求了,而在上圖中有一個用粉紅色的設定值
PowerManager/SystemIdleTimerReset
這個也請先有個印像,後面會談到這個部分。 搞定了不讓系統進入待機了,但是第二個問題就接著來了;系統不會進入待機模式了,但是背光的Timeout時間一到還是會關掉耶,能不能一直亮著?關掉我就看不到東西了啊。
那麼要解決這個問題,也是需要呼API,這時候我們會用到SetPowerReuirement、ReleasePowerRequirement,宣告的方式向下面這樣

     _
    Public Function SetPowerRequirement(ByVal pvDevice As String, ByVal DeviceState As PowerState, _
                                        ByVal DeviceFlags As Integer, ByVal pvSystemState As IntPtr, ByVal StateFlags As Integer) As IntPtr
    End Function

    ''reference http://msdn.microsoft.com/en-us/library/ms919803.aspx
     _
    Public Function ReleasePowerRequirement(ByVal hPowerReq As IntPtr) As Integer
    End Function
除了function的宣告之外,還有一些常數值必須要宣告

    Public Const POWER_FORCE As Integer = &H1000

    Public Enum PowerState
        PwrDeviceUnspecified = -1
        D0 = 0 'full on
        D1 = 1 'low power    
        D2 = 2 'standby    
        D3 = 3 'sleep    
        D4 = 4 'off    
        PwrDeviceMaximum = 5
    End Enum
使用的方式會像下面這樣,發出請求時

        hResult = SetPowerRequirement("BKL1:", PowerState.D0, POWER_NAME Or POWER_FORCE, Nothing, Nothing)
這邊這個hResult要記起來,因為使用完之後我們要釋放掉我們的請求,而BKL1:這個指的就是LCD的部分了;釋放的部分會像下面這樣

在做SetPowerRequirement的時候有幾個部分要注意
  • 利用這個API可以對系統發出請求,將power設定在normal或是sleep的狀態,也就是亮起或是關閉,亮一點或是暗一點是辦不到的
  • SetPowerRequirement可以接受多重呼叫,也就是有可能很多隻成是都會呼叫SetPowerRequirement來請求系統將power設定成某一種狀態,那打架的時候怎麼辦?如果發生打架的情形,系統會將Power設定在比較亮的狀態,也是是說,A程式要求D4的狀態,B程式要求D0的狀態,那最後Power會是D0
那麼SetPowerReurirement可以接受多重呼叫,也就表示我的呼叫不一定會成功,那有沒有比較惡霸的方式,不管三七二十一,變成我要的狀態就對了?
是有的,但是在說明這個方法之前,要先知道一些事情

WM系統會自動維護電源狀態,還記得上面利用SetPowerRequirement時會傳回一個intptr嗎?最後不使用的時候會利用這個intptr來做release的動作,那萬一如果程式異常結束(中止)的話,會怎麼樣?WM系統也會自動幫我們做釋放,所以不會造成不良影響;而接下來要說明的方式是強制把power設定到某一狀態,所以如果沒做釋放動作,那這個時候WM系統的電源管理機制會錯亂,其他應用程式也就跟著不能對該設備(BKL1:)做操作了,所以切記,一定要做釋放的動作。

比較惡霸的方式就是呼叫SetDevicePower,宣告的方式是下面這樣

     _
    Public Function SetDevicePower(ByVal pvDevice As String, ByVal dwDeviceFlags As Integer, ByVal DeviceState As Integer) As Integer
    End Function
呼叫的時候會像下面這樣

D0就是指定一直亮著的狀態,切記切記,使用完畢請記得要放回原位,記得用下面方式讓控制權回到WM系統

好啦,滿足了不睡覺、不熄大燈的需求之後,還有什麼呢?接下來就是來看看怎麼去控制系統關閉背光的時間;在WM系統中,一般會進到”設定”裡面去做修改,像是下圖

啥?最短只能設到1分鐘?當插上外部電源時,通常來說環境都是很明亮的,不能10秒後就關掉嗎? 這個時候,一樣先來這些相關的資料是存放再哪裡,先來看看登錄檔

HKEY_CURRENT_USER\ControlPanel\BackLight裡面,就可以看到相關的設定值了,下面只列出會用到的名稱跟功能
  • ACBrightness:當使用外部電源時,背光的亮度
  • ACTimeout:當使用外部電源時,沒有對裝置進行操作,經過多久(秒)後,關閉背光
  • Brightness:當使用電池電力時,背光的亮度
  • BatteryTimeout:當使用電池電力時,沒有對裝置進行操作,經過多久(秒)後,關閉背光
這邊會用到Timeout相關的設定,如果你從系統去做變更,也會在登錄檔裡面看到對應的變化(要refresh一下);好,那麼怎麼從程式去做控制呢?當然首先要先變更登陸檔裡面的設定值,但是光改變設定值,系統不知道我改了啊,所以還要喊一下系統,讓他知道有這件事,也就是觸發一個變更的事件,那麼程式碼會像下面這樣 API宣告

    Public Function CreateEvent(ByVal lpEventAttributes As IntPtr, ByVal bManualReset As Boolean, _
                               ByVal InitialState As Boolean, ByVal lpName As String) As IntPtr
    End Function

     _
    Public Function EventModify(ByVal hEvent As IntPtr, ByVal func As Integer) As Boolean
    End Function

     _
    Public Function CloseHandle(ByVal hObject As IntPtr) As Boolean
    End Function
使用時會像下面這樣

        If SetTimeout("ACTimeout", 5) Then
            TextBox1.Text = "Set timeout success"
        Else
            TextBox1.Text = "Set timeout failed"
        End If
    End Sub

    ''' 
    ''' 設定背光關閉時間
    ''' 
    ''' 值名稱,ACTimeout / BatteryTimeout
    ''' Timeout秒數
    ''' 
    ''' 
    Private Function SetTimeout(ByVal RegKeyName As String, ByVal Timeout As Integer) As Boolean
        Dim hUSER As Microsoft.Win32.RegistryKey
        hUSER = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("ControlPanel\BackLight", True)
        hUSER.SetValue(RegKeyName, Timeout, Microsoft.Win32.RegistryValueKind.DWord)
        hUSER.Flush()
        hUSER.Close()
        Return CreateChangeEvent("PowerManager/ReloadActivityTimeouts")
    End Function

    ''' 
    ''' 產生變更的事件
    ''' 
    ''' 事件名稱
    ''' 成功與否
    ''' 
    Private Function CreateChangeEvent(ByVal EventName As String) As Boolean
        Dim hEvent As IntPtr
        Dim bolReturn As Boolean = False
        hEvent = CreateEvent(IntPtr.Zero, False, True, EventName)
        bolReturn = EventModify(hEvent, enumEvent.EVENT_SET)
        CloseHandle(hEvent)
        Return bolReturn
    End Function
於是乎,我們就可以由程式裡面去控制多久之後去關閉背光了。

就在不久之前,從登錄檔裡面也可以看到調整背光亮度的設定值,那我是不是也可以如法炮製,利用同樣的方式去做調整呢?原則上是可以,為什麼說原則上呢?這又有一段故事要講..在背光的亮度調整部分,由於各家廠商硬體不同,所以會去修改到Driver,而當背光修改之後,一般需要去觸發BackLightChangeEvent,但是各家廠商實作之後這個Event名稱有可能就會不同了,目前在網路上查資料,有看到BackLightChangeEvent,也有看到SDKBackLightChangeEvent,而我的HTC Tytn2都不是這兩個..
所以這部分就沒有通用解了,下面說明一般的方式以及HTC變更的方式
一般方式

        If SetBrightness("ACBrightness", 10) Then
            TextBox1.Text = "Set Brightness success"
        Else
            TextBox1.Text = "Set Brightness failed"
        End If
    End Sub

    Private Function SetBrightness(ByVal RegKeyName As String, ByVal Level As Integer) As Boolean
        Dim hUSER As Microsoft.Win32.RegistryKey
        hUSER = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("ControlPanel\BackLight", True)
        hUSER.SetValue(RegKeyName, Level, Microsoft.Win32.RegistryValueKind.DWord)
        hUSER.Flush()
        hUSER.Close()
        Return CreateChangeEvent("BacklightChangeEvent")
    End Function
那麼HTC的部分怎麼辦呢?在不知道抓掉多少根頭髮之後,總算在xda上面找到這篇
http://forum.xda-developers.com/showthread.php?t=450318&page=31
直接呼叫dll來做調整的動作,所以在API的宣告上做了像下面這樣的Class


Imports System.Runtime.InteropServices

Public Class HTCPInvoke
    '' battery backlight
     _
    Public Shared Function HTCUtilGetOnBatteryBrightnessLevel(ByRef pValue As Integer) As Integer
    End Function

     _
    Public Shared Function HTCUtilSetOnBatteryBrightnessLevel(ByRef pValue As Integer) As Integer
    End Function

    '' AC backlight
     _
    Public Shared Function HTCUtilSetOnPowerBrightnessLevel(ByRef pValue As Integer) As Integer
    End Function

     _
    Public Shared Function HTCUtilGetOnPowerBrightnessLevel(ByRef pValue As Integer) As Integer
    End Function

    '' minimum
     _
    Public Shared Function HTCUtilGetBacklightMinBrightness(ByRef pValue As Integer) As Integer
    End Function

    '' maximum
     _
    Public Shared Function HTCUtilGetBacklightMaxBrightness(ByRef pValue As Integer) As Integer
    End Function
End Class
呼叫的範例像是下面這樣

        Dim intIn As Integer = 0
        intResult = HTCPInvoke.HTCUtilGetOnPowerBrightnessLevel(intIn)
        For i As Integer = 0 To 10 Step 2
            intIn = i
            intResult = HTCPInvoke.HTCUtilSetOnPowerBrightnessLevel(intIn)
            Threading.Thread.Sleep(2000)
        Next
本來對於背光調整這部分已經放棄了,都是spb mobile shell害的,他的小工具居然可以調整我的Tytn2!! (好叉燒,為什麼要讓我吃到這麼好吃的叉燒..),讓我不知道抓了幾天的頭,看了多少google上面的相關討論..

回到正題,那麼就結束了嗎?還沒有,朋友,再聽我多費話兩句;再上面的程式碼中可以去改變亮度,但是都有分是使用外部電源還是電池電源,那麼,我現在是用哪一種電?總不能每次都暴力兩個值都改吧,我們可是程式人,有潔癖的..那要怎麼知道目前是什麼裝態?於是乎,又要辛苦coredll.dll大俠了,宣告方式會像下面這樣

     _
    Public Structure SYSTEM_POWER_STATUS_EX2
        Public ACLineStatus As Byte
        Public BatteryFlag As Byte
        Public BatteryLifePercent As Byte
        Public Reserved1 As Byte
        Public BatteryLifeTime As Integer
        Public BatteryFullLifeTime As Integer
        Public Reserved2 As Byte
        Public BackupBatteryFlag As Byte
        Public BackupBatteryLifePercent As Byte
        Public Reserved3 As Byte
        Public BackupBatteryLifeTime As Integer
        Public BackupBatteryFullLifeTime As Integer
        Public BatteryVoltage As Integer
        Public BatteryCurrent As Integer
        Public BatteryAverageCurrent As Integer
        Public BatteryAverageInterval As Integer
        Public BatterymAHourConsumed As Integer
        Public BatteryTemperature As Integer
        Public BackupBatteryVoltage As Integer
        Public BatteryChemistry As Byte
    End Structure

    Public AC_LINE_ONLINE As Integer = 1
    Public AC_LINE_OFFLINE As Integer = 0

    ''reference http://msdn.microsoft.com/en-us/library/aa931066.aspx
     _
    Public Function GetSystemPowerStatusEx2(ByRef lpSystemPowerStatus As SYSTEM_POWER_STATUS_EX2, _
                                            ByVal dwLen As Integer, ByVal fUpdate As Boolean) As Integer
    End Function
使用方式會像下面這樣

        Dim Result As New SYSTEM_POWER_STATUS_EX2
        Dim intReturn As Integer = GetSystemPowerStatusEx2(Result, Marshal.SizeOf(Result), False)
        If intReturn <> 0 Then
            ''呼叫成功
            If Result.ACLineStatus = AC_LINE_ONLINE Then
                ''Using AC power
                TextBox1.Text = "using AC power"
            Else
                TextBox1.Text = "using battery power"
            End If
        End If
    End Sub
其實在GetSystemPowerStateEx2中,還有很多東西可以使用,這邊只抓出是否使用AC電源的部分,各位可以自行在觀察、研究一下。

呼 ~ 今天的介紹就到這邊了,快,別閒著,把手機掏出來試試看吧 !!