2012年4月10日 星期二

[TQC+ Android] 3-10 程式背景音樂 Use MediaPlayer






設計程式背景音樂 , 該音樂可在應用程式執行階段重複播放 , 當手機按下退出鍵或是程式失去焦點時 , 停止播放背景音樂 , 這題的內容在 [TQC+ Android] 3-3 MP3 播放器 Use MediaPlayer 都差不多講完了 , 沒甚麼特別的技巧 , 考試跳出這題的話算你賺到。




package COM.TQC.GDD03;

import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;


public class GDD03 extends Activity
{
  /** Called when the activity is first created. */
  
  MediaPlayer mPlayer;
  
 
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.main);    
     
     try
     {
      mPlayer = MediaPlayer.create(this, R.raw.light);
      mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
      
      mPlayer.setLooping(true);
      //重複播放
      
      //mPlayer.prepare();
      //特別使用註解的方式, 是為了提醒大家, 由於我們先前使用create method建立MediaPlayer
       //create method會自動的call prepare(), 所以我們再call prepare() method會發生 prepareAsync called in state 8的錯誤
         
  }catch (IllegalStateException e)
  {
  // TODO Auto-generated catch block
  e.printStackTrace();
  } 
  }
  
  @Override
  protected void onResume()
  {
 // TODO Auto-generated method stub   
   
   super.onResume();
   mPlayer.start();
  }

  @Override
  protected void onPause()
  {
 // TODO Auto-generated method stub   
   
   super.onPause();
   mPlayer.pause();
  }

  @Override
  protected void onDestroy()
  {
   // TODO Auto-generated method stub   
   super.onDestroy();   
   mPlayer.release();
  }  
}


這題連個Layout都不用設計 , AndroidManifest.xml 也不用特別額外宣告 , 所以都不附上 , 記得先把音樂檔放到 res/raw 資料夾底下就好。




P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....





[TQC+ Android] 3-9 畫布程式 Use Custom SurfaceView





設計一個全螢幕且背景為黑色的畫布程式 , 支援手指單點觸控 , 於手指按下螢幕時捕捉該觸控點的座標 , 以該觸控點座標為基準點繪製一個10*10的白色實心矩形 , 且每一次繪製時皆會清除前一次遺留的矩形 , 這題會使用自訂的SurfaceView , 並且實作處理手勢變化的 OnGestureListener 及實作一個Runnable 讓 non-UI Thread 幫我們處理Canvas的更新。


package COM.TQC.GDD03;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;


public class GDD03 extends Activity
{
  public static SView mSurfaceView01;
  public static SurfaceHolder mSurfaceHolder01;
  public GestureDetector detector;  
   

  /** Called when the activity is first created. */
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);  
    
    mSurfaceView01 = (SView) this.findViewById(R.id.surfaceView1);
    //從xml檔中取用我們的 CustomView
    
    detector = new GestureDetector(this,mSurfaceView01); 
    //GestureDetector 能幫我們監聽觸控螢幕上的手勢變化
    //它需要一個實作好的 GestureDetector.OnGestureListener interface
    //而這個例子中我們已經傳入 mSurfaceView01 , 因為它實作了 GestureDetector.OnGestureListener
   
  }  
  
  @Override
  public boolean onTouchEvent(MotionEvent event)
  {
 // TODO Auto-generated method stub
   
 //return super.onTouchEvent(event);
 //這是原本method 預設的回傳值 , 在這個例子使用 detector.onTouchEvent(event) 取代
    
 return detector.onTouchEvent(event);
 //GestureDetector 會根據傳入的MotionEvent 判斷手勢的變化 
 //另外它只會判斷Touch events 所以我們將Touch screen傳入的 events 
 //送到 GestureDetector class 的 onTouchEvent(event) , 讓它判斷是哪一種手勢
 //此外GestureDetector 不接受 trackball events  
 
  }
}


在這個class中先宣告Custom SurfaceView : mSurfaceView01 , mSurfaceView01 已經獨立出來存在另一個檔案 SView.java , 另外再使用 GestureDetector 替 mSurfaceView01 註冊監聽事件 , 讓 mSurfaceView01 可以根據手勢變化回應我們想要的動作。


當我們收到 Touch Screen 所傳入的Motion Events , 再將Events 傳入GestureDetector class 中的 onTouchEvent(event) method 去進一步的判斷是哪種手勢 , 再回應相對應的動作。



package COM.TQC.GDD03;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.GestureDetector.OnGestureListener;


public class SView extends SurfaceView implements SurfaceHolder.Callback , OnGestureListener , Runnable 
{ 
 SurfaceHolder mSurfaceHolder01;
 Canvas mCanvas;
 
 //我們已經在xml中定義了自訂的 SurfaceView , 所以系統會選用這個建構子去產生instance
 public SView(Context context, AttributeSet attrs)
 {
  // TODO Auto-generated constructor stub
  
  super(context,attrs);  
  
  mSurfaceHolder01 = this.getHolder();
  //獲取holder , 用來存取及控制 SurfaceView
  
  mSurfaceHolder01.addCallback(this);
  //監聽SurfaceHolder的事件
  
  setFocusable(true);
 } 

 @Override
 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
 {
  // TODO Auto-generated method stub  
 }

 @Override
 public void surfaceCreated(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub 
  
  new Thread(this).start();
  //當Surface 被建立時 , 產生一個Thread來處理畫布內容的改變
 }

 @Override
 public void surfaceDestroyed(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub
  
  ThreadFlag = false;
  //終止Thread 的運作  
 }
 
 @Override
 public void onDraw(Canvas canvas)
 {   
  // onDraw() method 已經有 SurfaceHolder.lockCanvas() 的效果了
  // 我們也不用額外的對canvas 執行unlock 的動作
  
  canvas.drawColor(Color.BLACK);
  //把畫布填充為黑色    
  Paint p = new Paint();
  p.setColor(Color.WHITE);
       
  if(DrawFlag) canvas.drawRect(x-5, y-5, x+5, y+5, p);
  // 畫一個矩形  
     
  DrawFlag = false;
  //關閉更新畫布開關 , 直到下一次的Touch 事件發生
 }

 float x , y;
 boolean ThreadFlag = true , DrawFlag = false;

 @Override
 public boolean onDown(MotionEvent arg0)
 {
  // TODO Auto-generated method stub
  
  x = arg0.getRawX();
  y = arg0.getRawY();
  //獲取觸控的座標
  
  DrawFlag = true;
  //開啟更新畫布的開關 
  
  return true;
 }
 
 @Override
 public void run()
 {
  // TODO Auto-generated method stub
  
  while(ThreadFlag)
  {
   if(DrawFlag) this.postInvalidate();
   //由於我們是在non-UI Thread中要求要更新畫布
   //所以使用 postInvalidate() method
  }
 }  

 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
   float velocityY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onLongPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
   float distanceY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onShowPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onSingleTapUp(MotionEvent e)
 {
  // TODO Auto-generated method stub  
  Log.d("Test", "onSingleTapUp");
  return true;
 }
}


至於我們自訂的SurfaceView 它是如何運作的? 我們會在Surface Created 的時候開始run一個non-UI Thread , 這個Thread 專門負責去呼叫 postInvalidate() method 來更新Canvas , 此外postInvalidate() method 會去執行 onDraw() method , 所以我們將畫布要呈現的內容都寫在onDraw() 中等著被執行 , 此外使用一個flag來控制畫布更新的時機 (是在觸控到螢幕時) , 此外當Surface Destroyed 的時候終止Thread 的運作。




接下來是 main.xml的部分 , 請注意我們是使用自訂的SurfaceView
    










P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....







2012年4月8日 星期日

[TQC+ Android] 3-8 判斷地標所屬區域 Use LocationManager




設計判斷地標所屬區域 , 設計一個判讀程式 , 判斷使用者手持手機時 , 該使用者是否在小巨蛋的區域內 , 一旦手機進入或是走出小巨蛋的區域 , 即變更TextView 的提示文字 , 以下是程式部分。



package COM.TQC.GDD03;


import android.app.Activity;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;


public class GDD03 extends Activity implements LocationListener
{
  public String strLocationProvider = "";
  public TextView mTextView01;
  LocationManager LManager;  
  double latitute , longtitute;
    
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    mTextView01 = (TextView)findViewById(R.id.myTextView1); 
    getLocationPrivider();
  }
  
  public void getLocationPrivider()
  {
    try
    {
      Criteria mCriteria01 = new Criteria();
      mCriteria01.setAccuracy(Criteria.ACCURACY_FINE);
      //定位精準標準 , Criteria.ACCURACY_FINE 為精確      
      mCriteria01.setAltitudeRequired(false);
      //是否需要海拔高度的資訊
      mCriteria01.setBearingRequired(false);
      //是否需要方向資訊
      mCriteria01.setCostAllowed(true);
      //是否同意用到付費的GPS Provider
      mCriteria01.setPowerRequirement(Criteria.POWER_LOW);
      //設定定位所需要的的電力等級
      
      LManager = (LocationManager) this.getSystemService(LOCATION_SERVICE);
      strLocationProvider = LManager.getBestProvider(mCriteria01, true);
      //依照我們上面所制定的要求 , 取得最佳的定位方式(定位衛星)
      //由於我是使用模擬器 , 所以回傳的定位提供者是Networks
      //由於我們想要的是一個GPS , 所以之後我們會使用 LocationManager.GPS_PROVIDER
        
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }  

  @Override
  protected void onResume()
  {
 // TODO Auto-generated method stub
 super.onResume();
 
 Location location = LManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
 //透過 getLastKnownLocation() method 可以得知上一次定位提供者所提供的Location資訊
 //所以程式在一開始執行時就可以判斷上一次的座標是否落在小巨蛋內
 
 if(location != null) getWhere(location);
 
 LManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 100, this);
 //註冊監聽事件 , 最小更新區間為5000 milisecond , 100 meter
 
  }
  
  @Override
  protected void onPause()
  {
 // TODO Auto-generated method stub
 super.onPause();
 LManager.removeUpdates(this);
 //移除更新座標的要求
  }
  
  @Override
  public void onLocationChanged(Location location)
  {
 // TODO Auto-generated method stub
 getWhere(location);
 //當偵測到座標改變時會取得座標
  }

  private void getWhere(Location location)
  {
 // TODO Auto-generated method stub
 latitute = location.getLatitude();
 longtitute = location.getLongitude();
 
 Log.d("Test", "getLatitude():"+latitute+" getLongitude()"+longtitute);
 
 //以下是判斷座標是否座落在小巨蛋內的方法
 
 if(latitute > 25.051578 || latitute < 25.051201 || longtitute>121.549666 || longtitute<121.549197)
    {
  //以這四個點作判斷剛好可以圍成一個長方形的區域 , 區域外的部分一定是小巨蛋的區域之外
  //但是這個長方形內仍有一小部分的區域仍不屬於小巨蛋 , 剩下這一小部分區域是一直角三角形
     mTextView01.setText("Outside Taipei Area");
    }
    else
    {
     //接下來我們可以使用向量外積的方式來判斷座標是否落在這直角三角形內 , CrossProducts 超過 0 代表落在直角三角形內也就是不在小巨蛋裡
     
     double ModelLineVectorX = 25.051216 - 25.051201 , ModelLineVectorY = 121.549666 - 121.549197;
     double UserLineVectorX = latitute - 25.051201 , UserLineVectorY = longtitute - 121.549197;
     
     double CrossProducts = UserLineVectorX * ModelLineVectorY - ModelLineVectorX * UserLineVectorY;
     
     if(CrossProducts <= 0) mTextView01.setText("Within Taipei Area");
     else mTextView01.setText("Outside Taipei Area"); 
    }
  }

  @Override
  public void onProviderDisabled(String provider)
  {
 // TODO Auto-generated method stub 
  }

  @Override
  public void onProviderEnabled(String provider)
  {
 // TODO Auto-generated method stub 
  }

  @Override
  public void onStatusChanged(String provider, int status, Bundle extras)
  {
 // TODO Auto-generated method stub 
  }    
}



再來是 AndroidManifest.xml , 因為我們要存取使用者的座標還要使用網路 , 所以要記得去註冊所需要的 use-permission。



    
    
    
    
    
        
            
                
                
            
        

    
    

 



使用模擬器來發送模擬的座標







P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....




[Android] 使用 custom action strings 判斷簡訊發送狀態 Use SmsManager, PendingIntent





BroadcastReceiver的使用有兩種方法 , 之前文章所使用的技巧是動態的註冊和註銷 : [TQC+ Android] 3-7 判斷髮送簡訊狀態 Use registerReceiver, SmsManager, PendingIntent , 而這篇要使用的是在 AndroidManifest.xml 中靜態註冊 , 並在<intent-filter> tag 宣告使用我們自訂的action strings , 以下是程式碼。


package COM.TQC.GDD03;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class GDD03 extends Activity
{
  private Button mButton1;
  private EditText mEditText1, mEditText2;
  
  PendingIntent sentIntent;
  SmsManager smsManager;  
    
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    /* Phone No. */
    mEditText1 = (EditText) findViewById(R.id.myEditText1);
    
    /* SMS BODY */
    mEditText2 = (EditText) findViewById(R.id.myEditText2);
    mButton1 = (Button) findViewById(R.id.myButton1);
    
    mEditText1.setText("5556");
    mEditText2.setText("DAVID_SEND");
    
    smsManager = SmsManager.getDefault();
    //SmsManager 類別可以協助我們送出簡訊 , 我們可以透過 這個static method getDefault()來取得 SmsManager預設的instance
    
    Intent smsIntent = new Intent("COM.TQC.GDD03.Send_Message");
 //替這個Intent 設定我們自訂的action : COM.TQC.GDD03.Send_Message
    //通常在action前我們會附加上 application package name
    
    smsIntent.putExtra("Data", "Hello!");
    //順便附加個字串
    
 sentIntent = PendingIntent.getBroadcast(GDD03.this, 0, smsIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 //public static PendingIntent getBroadcast (Context context, int requestCode, Intent intent, int flags)
 //使用這個static method 我們會接收到一個具有像Context.sendBroadcast()行為的PendingIntent instance.
 //我們使用 FLAG_CANCEL_CURRENT , 用意是在產生一個新的 PendingIntent之前 , 會把舊的先cancel掉
 
    
    mButton1.setOnClickListener(new Button.OnClickListener()
    {
      @Override
      public void onClick(View v)
      {
        // TODO Auto-generated method stub
     smsManager.sendTextMessage(mEditText1.getText().toString(), null, mEditText2.getText().toString(), sentIntent, null);
     //送出簡訊 , 並附加一個 PendingIntent sentIntent , 用意是等它回報傳送結果(result code)
     //並且可以從sentIntent中取得相關資訊 , 例如一些額外的錯誤提示 , 或是我們預先加入的資訊如之前的"Data" 
      
      }
    });
  } 
  
  public static class mServiceReceiver extends BroadcastReceiver
  {
   @Override
      public void onReceive(Context context, Intent intent)
      {
        // TODO Auto-generated method stub
       
       Log.d("Test","Data:"+intent.getStringExtra("Data"));
       //顯示之前擺入sentIntent的附加資訊
       
       if(intent.getAction().equals("COM.TQC.GDD03.Send_Message"))
       { 
        switch(getResultCode()) //取得回傳的result code
        {
         case Activity.RESULT_OK:       
         Toast.makeText(context, "SMS sent", Toast.LENGTH_LONG).show();
         break;
        
         case SmsManager.RESULT_ERROR_RADIO_OFF:
         Toast.makeText(context, "RADIO OFF", Toast.LENGTH_LONG).show();
            break;
         
         case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
         //如果是這個"一般錯誤"的case , 那麼 sentIntent 也許會附帶一組 "errorCode" 來記錄錯誤資訊
         break;
         
         case SmsManager.RESULT_ERROR_NULL_PDU:
            //Failed because no pdu provided 
            break;
            
         case SmsManager.RESULT_ERROR_NO_SERVICE:
            //Failed because service is currently unavailable
            break;
        
         default:       
         Toast.makeText(context, "NO SERVICE", Toast.LENGTH_LONG).show();
            break;
        }      
       }
      }
  }
}


程式的處理流程在註解中已經有提到了 , 不清楚的可以搭配之前的文章。

我們將 custom BroadcastReceiver 定義為 public static 一個內部靜態類別 , 又或者可以另外 custom BroadcastReceiver 單獨分開擺在另一個 Java 檔中 , 以上兩個不同的方式在AndroidManifest.xml 中也搭配不一樣的宣告內容。

    
        
            
                
                
            
        
        
            
                
            
               
    
    

 
注意 <receiver android:name="GDD03$mServiceReceiver"> , 因為我們將 mServiceReceiver 定義為內部靜態類別 , 而  GDD03$mServiceReceiver  是一個標準的方法 , 可以協助我們取用內部類別。


2012年4月7日 星期六

[TQC+ Android] 3-7 判斷發送簡訊狀態 Use registerReceiver, SmsManager, PendingIntent





設計一發送簡訊的程式 , 並可得知發送之後的送達結果 , 這題使用BroadcastReceiver 等待程式發送簡訊的動作 , 而關於BroadcastReceiver的使用有兩種方法 , 一種是靜態的在AndroidManifest.xml中在<receiver> tag中註冊 , 另一種是動態的在程式中使用 registerReceiver() method 註冊和使用unregisterReceiver()註銷 , 另外既然要讓 BroadcastReceiver 可以判斷發送簡訊的動作 , 就要在 IntentFilter 中增加發送簡訊的action , 但是 Android SDK 沒有提供相關的action , 所以我們必須使用我們自己的 custom action string , 而這題我們使用動態註冊及註銷的方式搭配自訂的 action 來達成題目要求。




package COM.TQC.GDD03;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class GDD03 extends Activity
{
  private Button mButton1;
  private EditText mEditText1, mEditText2;
  
  PendingIntent sentIntent;
  SmsManager smsManager;
  mServiceReceiver mServiceReceiver;
    
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    /* Phone No. */
    mEditText1 = (EditText) findViewById(R.id.myEditText1);
    
    /* SMS BODY */
    mEditText2 = (EditText) findViewById(R.id.myEditText2);
    mButton1 = (Button) findViewById(R.id.myButton1);
    
    mEditText1.setText("5556");
    mEditText2.setText("DAVID_SEND");
    
    mServiceReceiver = new mServiceReceiver();
    //宣告一個自訂的BroadcastReceiver , 稍後我們會在onResume() 動態註冊它
    
    smsManager = SmsManager.getDefault();
    //SmsManager 類別可以協助我們送出簡訊 , 我們可以透過 這個static method getDefault()來取得 SmsManager預設的instance
    
    Intent smsIntent = new Intent("COM.TQC.GDD03.Send_Message");
 //替這個Intent 設定我們自訂的action : COM.TQC.GDD03.Send_Message
    //通常在action前我們會附加上 application package name
    
    smsIntent.putExtra("Data", "Hello!");
    //順便附加個字串
    
 sentIntent = PendingIntent.getBroadcast(GDD03.this, 0, smsIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 //public static PendingIntent getBroadcast (Context context, int requestCode, Intent intent, int flags)
 //使用這個static method 我們會接收到一個具有像Context.sendBroadcast()行為的PendingIntent instance.
 //我們使用 FLAG_CANCEL_CURRENT , 用意是在產生一個新的 PendingIntent之前 , 會把舊的先cancel掉
 
    mButton1.setOnClickListener(new Button.OnClickListener()
    {
      @Override
      public void onClick(View v)
      {
        // TODO Auto-generated method stub       
       
       smsManager.sendTextMessage(mEditText1.getText().toString(), null, mEditText2.getText().toString(), sentIntent, null);
       //送出簡訊 , 並附加一個 PendingIntent sentIntent , 用意是等它回報傳送結果(result code)
       //並且可以從sentIntent中取得相關資訊 , 例如一些額外的錯誤提示 , 或是我們預先加入的資訊如之前的"Data" 
      }
    });
  }
  
  public class mServiceReceiver extends BroadcastReceiver
  {
    @Override
    public void onReceive(Context context, Intent intent)
    {
      // TODO Auto-generated method stub
     
     Log.d("Test","Data:"+intent.getStringExtra("Data"));
     //顯示之前擺入sentIntent的附加資訊
     
     if(intent.getAction().equals("COM.TQC.GDD03.Send_Message"))
     { 
      switch(getResultCode()) //取得回傳的result code
      {
       case Activity.RESULT_OK:
       showToast("SMS sent");
       break;
      
       case SmsManager.RESULT_ERROR_RADIO_OFF:
       showToast("RADIO OFF");
       break;
       
       case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
       //如果是這個"一般錯誤"的case , 那麼 sentIntent 也許會附帶一組 "errorCode" 來記錄錯誤資訊
       break;
       
       case SmsManager.RESULT_ERROR_NULL_PDU:
          //Failed because no pdu provided 
          break;
          
       case SmsManager.RESULT_ERROR_NO_SERVICE:
          //Failed because service is currently unavailable
          break;
      
       default:
       showToast("NO SERVICE");
       break;
      }      
     }
    }
  }
   
  @Override
  protected void onResume()
  {
    // TODO Auto-generated method stub
    super.onResume();
    IntentFilter IFilter = new IntentFilter();
    IFilter.addAction("COM.TQC.GDD03.Send_Message");
    //宣告一個IntentFilter並使用我們之前自訂的action
    
    registerReceiver(mServiceReceiver,IFilter);
    //動態註冊BroadcastReceiver
  }
  
  @Override
  protected void onPause()
  {
    // TODO Auto-generated method stub
    super.onPause();
    unregisterReceiver(mServiceReceiver);
    //動態註銷BroadcastReceiver
  }
  
  public void showToast(String message)
  {
   Toast.makeText(this, message, Toast.LENGTH_LONG).show();
  }
}


在這裡有幾個重點要說明一下 , 我們選擇在 onResume() method去動態註冊custom BroadcastReceiver : mServiceReceiver , 然後選擇在onPause()的時候去註銷它 , 那是因為當程式進入pause狀態時 , 是沒辦法接收到 intent的。


另外根據PendingIntent的操作行為 , 我們選用不同的method 取得它的instance , 而它有三個不同的 static method可以協助我們取得instance , 分別是 getActivity(Context, int, Intent, int)getBroadcast(Context, int, Intent, int),getService(Context, int, Intent, int) 在這題我們要讓mServiceReceiver 可以接收到傳送簡訊的action , 所以使用getBroadcast() , 而 PendingIntent 就會附加在 SmsManager.sendTextMessage() 當傳送簡訊時被sent出去


SmsManager 類別中的 
public void sendTextMessage (String destinationAddress, String scAddress, String text, PendingIntent sentIntent,PendingIntent deliveryIntent) 可以協助我們送出簡訊 , 特別是如果參數 PendingIntent sentIntent 不是null的話 , 當傳送簡訊後可以透過 getResultCode() 取得回報的 result code , 我們再根據這個 result code去判斷簡訊發送的情形是否成功 , 或是有哪些錯誤訊息。




接下來是AndroiManifest.xml , 請記得由於我們有發送簡訊這個動作 , 所以記得宣告 use-permission : android.permission.SEND_SMS

    
        
            
                
                
            
        
    
    

 




最後是main.xml 

  
  
  
  
  
  
  
  
  
  
  














由於題目要求能判斷無線網路關閉的狀態 , 所以去設定無線裝置及網路的地方啟動飛航模式。



成功的判斷出因為沒有網路所以沒辦法成功發送簡訊的狀態




開啟另一個5556模擬器 , 可以看到畫面上有出現接收到簡訊的提示。



P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....



Google Analytics