2013年5月25日 星期六

[Win32] 開關螢幕及鎖定(Screen On/Off and Lock)

開啟及關閉螢幕(Screen On/Off)
我們利用SendMessage來廣播WM_SYSCOMMAND訊息給所有視窗(windows),
然後代入SC_MONITORPOWER這個參數來設定顯示狀態,設定值如下面描述。

SC_MONITORPOWER
  • -1 (開啟螢幕)
  • 1  (讓螢幕處於低耗電量)
  • 2  (關閉螢幕)

下面是程式碼片段,注意最後一個參數的設定,代表不同功能。
void screenOff()
{
    SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER,(LPARAM)2);
}

void screenOn()
{
    SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER,(LPARAM)-1);
}

void screenLowPower()
{
    SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER,(LPARAM)1);
}

鎖定(Lock)
鎖定的功能就和按下 Ctrl+Alt+Del,然後在選擇"鎖定"一樣
void lock()
{
    LockWorkStation();
}

[Win32] 關機及重開機(shutdown and restart)

關機和重開機(shutdown and restart)首先要先調高你的程序(process)到可以關機及重開機的權限,
然後在利用這個InitiateSystemShutdown API來達到關機和重開的功能。

我們來介紹一下InitiateSystemShutdown這個API,依照MSDN的描述,

BOOL WINAPI InitiateSystemShutdown(
_In_opt_ LPTSTR lpMachineName,
_In_opt_ LPTSTR lpMessage,
_In_ DWORD dwTimeout,
_In_ BOOL bForceAppsClosed,
_In_ BOOL bRebootAfterShutdown
);

lpMachineName [in, optional]: 如果您想要關閉另一台電腦,在這裡則要輸入那台電腦的網路名稱;反之如果設成NULL,則表示關閉當下這台電腦。
lpMessage [in, optional]: 這裡是用來設定關機對話框的內容描述。
dwTimeout [in]: 搭配第二個參數,用來設定對話框的停留的時間,如果設定成零的話,就不會顯示對話框,然後直接關機。
bForceAppsClosed [in]:如果為TRUE,應用程式不會被存檔,即會強迫馬上關機,所以資料有可能會遺失。
如果為FALSE,系統會顯示一個對話框,來引導使用者去關閉其它已開啟的應用程式。
bRebootAfterShutdown [in]:如果為TRUE為重開的意思,如果為FALSE則是關機。

程式碼如下:
void shutdown()
{
 HANDLE hToken; 
 TOKEN_PRIVILEGES tkp; 
 BOOL fResult; 

 // Get the current process token handle so we can get shutdown privilege.
 if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
  printf("OpenProcessToken failed.");

 // Get the LUID for shutdown privilege.
 LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);
 tkp.PrivilegeCount = 1; // one privilege to set
 tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

 // Get shutdown privilege for this process.
 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0);

 if (GetLastError() != ERROR_SUCCESS)
  printf("AdjustTokenPrivileges enable failed.");

 fResult = InitiateSystemShutdown( NULL, NULL, 0, FALSE, FALSE); //最後一個參數,TRUE表示重開機,FASLE表示關機
 if (!fResult)
 {
  printf("InitiateSystemShutdown failed.");
 }

 // Disable shutdown privilege.
 tkp.Privileges[0].Attributes = 0;
 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,(PTOKEN_PRIVILEGES) NULL, 0);

 if (GetLastError() != ERROR_SUCCESS)
 {
  printf("AdjustTokenPrivileges disable failed.");
 } 
}

[Win32] 休眠及睡眠(hibernate and suspend)

剛好用到幾個電源管理的APIs,先簡單介紹一下休眠及睡眠。
休眠和睡眠有什麼不同呢?

睡眠(suspend)會讓電腦處於一個低耗電量的模式,但當電源被用光時,
電腦還是會關機的,所以沒有被儲存的資料還是會遺失。

休眠(hibernate)則會將電腦當下的狀態存到硬碟裡,然後power off;
當喚醒時(resume),則會回存power off前狀態到記憶體裡(RAM)。
舉個例子,當你開啟了office word和excel,然後按下休眠,再喚醒時,依然會看到word和excel;
反之,如果是重開機,就看不到啦。

實作上會用到一個API,SetSuspendState
BOOLEAN WINAPI SetSuspendState(
_In_ BOOLEAN Hibernate,
_In_ BOOLEAN ForceCritical,
_In_ BOOLEAN DisableWakeEvent
);

Hibernate [in] : 如果是TRUE,系統將進入hibernates;反之則是suspended。
ForceCritical [in] : 如果是TRUE,系統則立刻suspend;
反之系統會先廣播一個PBT_APMQUERYSUSPEND事件給每一個應用程式,請求要suspend的權限。
DisableWakeEvent [in] : 如果是TRUE,系統將關掉所有wake event,反之wake events得是開啟的。

程式碼如下:
void suspend()
{
   //If the first parameter is TRUE, the system hibernates. 
  //If the parameter is FALSE, the system is suspended.
   SetSuspendState(FALSE, TRUE, FALSE); 
}

void hibernation()
{
   SetSuspendState(TRUE, TRUE, FALSE); 
}

2013年5月19日 星期日

[Android] 計時器(Chronometer)

1.在使用計時器時,有一個地方需要注意,
就是setBase,這個方法是用來設定一個基準點,也就是我從這個基準點開始計時。

假設我們的程式是這樣寫,就會發生問題,
當我們呼叫start()後,等到過了25秒,按下停止,
再過了15秒後,我們再按下開始讓它繼續計時,
就會發現不是從25秒往下算,而是從40秒開始算。
這是因為計時器從第一次按下開始的時間做為基準點,所以是40秒(25+15)
  btnStart.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    chronometer.start();
   }
  });

  btnStop.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    chronometer.stop();
   }
  });


2.為了解決上述的問題,我們需要額外利用一個參數(escapeTime)及setBase方法
在按下停止時先記錄已經跑過的時間,
然後待下一次按開始繼續計時的時候,在加上去
MainActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Chronometer;

public class MainActivity extends Activity {
 long escapeTime = 0;

 /* Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer1);
  final Button btnStart = (Button) findViewById(R.id.button1);
  final Button btnStop = (Button) findViewById(R.id.button2);
  final Button btnReset = (Button) findViewById(R.id.button3);

  btnStart.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {

    // SystemClock.elapsedRealtime()方法會回傳從開機到現在的時間
    // 把這個時間做為一個Base,從這個Base開始計時
    chronometer.setBase(SystemClock.elapsedRealtime() + escapeTime);
    chronometer.start();
   }
  });

  btnStop.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    //getBase是用來取得上一次setBase()的時間"chronometer.setBase(SystemClock.elapsedRealtime() + escapeTime);"
    escapeTime = chronometer.getBase() - SystemClock.elapsedRealtime();
    chronometer.stop();
   }
  });

  btnReset.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    chronometer.setBase(SystemClock.elapsedRealtime());
    chronometer.stop();
    escapeTime = 0;
   }
  });

 }

}


3.layout即拉入一個計時器,三個按鈕。
res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Chronometer
        android:id="@+id/chronometer1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dp"
        android:text="Chronometer" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="開始(start)" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="停止(stop)" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="18dp"
        android:text="重置(reset)" />

</LinearLayout>
結果:

2013年5月17日 星期五

[Android] 鬧鐘(AlarmMamager and PendingIntent) 下篇

上篇鬧鐘的片段
Intent intent = new Intent(MainActivity.this,
      PlayReceiver.class);
    intent.putExtra("msg", "play_voice");
    intent.addCategory(String.valueOf(SystemClock.elapsedRealtime()));

    long elapsed = SystemClock.elapsedRealtime() + 60 * 1000; // millis

    PendingIntent pi = PendingIntent.getBroadcast(
      MainActivity.this, 1, intent,
      PendingIntent.FLAG_UPDATE_CURRENT);

    AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
    am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapsed, pi);

當我們設定鬧鐘後,如果想取消的話呢?
很簡單,只要呼叫cancel function即可
am.cancel(pi);

那如何確認鬧鐘是否真的被取消呢?
主要是利用PendingIntent.FLAG_NO_CREATE這個參數,
當被包起來的Inent不存在,得回傳null
 
  boolean alarmUp = (PendingIntent.getBroadcast(MainActivity.this, 1,
    intent, PendingIntent.FLAG_NO_CREATE) != null);

  if (alarmUp) 
      Log.d("myTag", "Alarm is already active");
  else
      Log.d("myTag", "Alarm is not already active");

2013年5月16日 星期四

[Android] 鬧鐘(AlarmMamager and PendingIntent) 上篇

我們利用AlarmManager實作一個鬧鐘,
讓他在開機後1分鐘後,發出鈴響。
只要利用PendingIntent及AlarmManager就可以做到這樣的功能。

1. MainActivity.java
import android.os.Bundle;
import android.os.SystemClock;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
 Button mButton;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  mButton = (Button) findViewById(R.id.button1);
  mButton.setOnClickListener(new Button.OnClickListener() {

   @Override
   public void onClick(View v) {
    
    Intent intent = new Intent(MainActivity.this, PlayReceiver.class);
    intent.putExtra("msg", "play_voice");
    intent.addCategory(String.valueOf(SystemClock.elapsedRealtime()));
     //SystemClock.elapsedRealtime()會回傳從開機到現在當下所花的時間,手機進入睡眠時間也算在內(單位milliseconds)
    long elapsed = SystemClock.elapsedRealtime() + 60 * 1000; //60秒
    // 發送一個broadcast,類似 Context.sendBroadcast()
    // PendingIntent.FLAG_UPDATE_CURRENT參數表示,如果已存在 PendingIntent,就更新 extra data.
    PendingIntent pi = PendingIntent.getBroadcast(MainActivity.this, 1, intent, 
      PendingIntent.FLAG_UPDATE_CURRENT);
    
    AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
    am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapsed , pi);

   }

  });
 }


}
那Intent,和PendingIntent有什麼不同呢?
一般的Intent,用startActivity(intent)就會直接去啟動和intent。
PendingIntent可以想成延遲Intent,它則是先把某個Intent包好,丟給某個程式,等到一段時間後再去執行Intent。
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapsed , pi);
等到第二個參數(elapsed)所設定的時間到達時,就會執行pi這個PendingIntent
這裡有一點要注意,當 AlarmManager 執行 set() 時,Android 系統會比對已註冊的其他 Intent 的 action、data、type、class、category, 如果這幾個屬性完全相同,則系統會將這兩個 Intent 視為一樣, 這時系統會視 PendingIntent.FLAG_xxx 參數以決定如何處理這個新註冊的 Intent 。

2.接下來建立接收廣播的class:
PlayReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;

public class PlayReceiver extends BroadcastReceiver {

 private SoundPool sp;
 private int sourceid;

 @Override
 public void onReceive(Context context, Intent intent) {
  
  Bundle bData = intent.getExtras();
 
  if (bData.get("msg").equals("play_voice")) {
   sp = new SoundPool(10, AudioManager.STREAM_MUSIC, 5);
   sourceid = sp.load(context, R.raw.harm, 1);
   
   playSounds(1, context);
  }
  
 }

 public void playSounds(int repeatTime, Context context) {
  AudioManager am = (AudioManager) context.getApplicationContext()
    .getSystemService(Context.AUDIO_SERVICE);
  // 獲取最大音量
  float audMaxVolumn = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
  // 獲取目前音量
  float audCurrentVolumn = am.getStreamVolume(AudioManager.STREAM_MUSIC);
  // 左右聲道值範圍為 0.0 - 1.0
  float volRatio = audCurrentVolumn / audMaxVolumn;
  // 下面參數分別為播放音頻,左聲道,右聲道,設置優先級,重撥次數,速率(速率最低0.5,最高為2,1代表正常速度)
  sp.play(sourceid, volRatio, volRatio, 1, repeatTime, 1);
 }
}
播放音效的作法,請參考先前的文章

3. 在Layout裡加入一個按鈕, 來啟動鬧鐘
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_marginBottom="54dp"
        android:text="1分鍾後,鬧鍾啟動" />

</RelativeLayout>
4. 最後在AndroidManifest.xml 的 application 區段寫入要接收廣播的程式:
<receiver android:name="com.example.s.PlayReceiver" >
            <intent-filter>
                <action android:name="play_voice" />
            </intent-filter>
</receiver>

reference
http://oldgrayduck.blogspot.tw/2012/10/androidalarmmanager.html

2013年5月12日 星期日

[Android] 避免彈出的鍵盤檔到EditText (Move layouts up when soft keyboard is displayed)

有時候當鍵盤彈出的時候,會擋到輸入文字的欄位(EditText),
像下面這張圖一樣,輸入Number的欄位有一小部分被鍵盤檔住了。


為避免這樣的情形,我們可以在AndroidManifest.xml,
加入android:windowSoftInputMode="adjustPan",
這個屬性會保持您的游標所在處,不會被鍵盤給檔住,
所以使用者可以看到他正在輸入的文字。
    <activity name="MainActivity"
        android:windowSoftInputMode="adjustPan">
        ...
    </activity>

結果:
我們可以看到當鍵盤彈出時,
整個畫面layout會被向上推,
所以輸入欄位的地方就不會被擋到啦。