2012年10月3日 星期三

[Android] 如何使用 PreferenceActivity 及宣告Preference.xml (How to use PreferenceActivity and define Preference.xml)





        如果你想在app 中加入Setting 的功能讓使用者可以修改app 的功能和行為, 甚至將Setting value同步到 System Setting , Preference APIs 是一個不錯的選擇, 它的UI呈現方式就像 ListView 一樣, 而每個在ListView 中的subView 都可以宣告一個Key 為的是可以在SharedPreference 中存取資料, 如此一來Preference API 就會自動的幫我們儲存每個subView資料的變化, 讓我們省了不少功夫。


首先第一步要做的就是定義 XML, 將Setting 中會出現的各種Preference object 使用階層式的方式在XML 中定義清楚, 我們在 res/xml 中產生一個 XML 檔案preference.xml 如下:




在XML 檔案中 root element 必定為 PreferenceScreen, 代表著一個全新的Setting 畫面並且在其中加入我們所需要的Preference objects, 通常我們只會用到一個XML 檔案, 因為如果有需要新的Setting 畫面需求, 可以在root PreferenceScreen 中加入另一個PreferenceScreen 形成巢狀Setting。





我們使用Eclipse editor 的 Structure mode來編輯preference.xml, 這樣對於preference.xml 階層式的架構就非常清楚了, 所有的 Preference objects 都在root Preference中, 另外還加入了一個Second PreferenceScreen, 當使用者點選時會跳出一個新的Setting 畫面, 無需create 一個額外activity。

接下來介紹幾個常用的 attribute:





android:key
最重要的屬性, 必須是一個唯一的Key供SharedPreference 儲存setting value使用, 之後在PreferenceActivity 中我們也是靠Key 來識別不同的Preference object。
android:title
Preference object 出現在Setting 畫面中的名稱。
android:defaultValue
  SharedPreference 中儲存的初始值

android:summary
  Preference object 出現在Setting 畫面中的說明文字, 以較小的字出現在title 下方。



接下來先附上完整的 preference.xml, 再來針對每個Preference object 作介紹




    
        
    
        
        
    
    
        
    
    
        
        
          




EditTextPreference : 當使用者選擇時會跳出Dialog, 而關於這個Dialog 的屬性可以使用 Editor 事先編輯, 特別的是EditText 能宣告的屬性, EditTextPreference 也能拿來使用, 所以在這邊宣告了 inputType 屬性為只能輸入Decimal number, 另外我們將使用者輸入的數字顯示在summay 中, 不過這邊需要在PreferenceActivity 中加入此功能, 這部分稍後會提到。




ListPreference : 顯示一組有著Radio button 的List, 要使List 能呈現內容的話, 要事先為List import Entry array和Entry value array, 我們可以在 /res/value 中宣告 string array 再使用Editor 引入即可, array 內容如下:


    PreferenceActivity
    Hello world!
    Settings
    MainActivity
         
        First item    
        Second item    
        Third item    
     
        
        0    
        1    
        2    
     


Entry 宣告了要顯示在List 中的內容, Entry value 對應為每個 Entry 內容所代表的value
另外值得一提的是因為選擇ListPrefence時會跳出Dialog 所以如果在Editor 中再宣告Dialog Message 屬性時就會導致顯示內容錯誤, 另外Dialog 只會出現Negative Button !





RingtonePreference : 讓使用者選擇手機上的鈴聲, 將被選擇鈴聲的URI 以string 格式儲存在SharedPreference 中, showSilent 屬性表示列表中是否包含靜音的選項, ringtoneType 用來限制列表中顯示的鈴聲類型。


PreferenceCategory : 單純的將Prefence object 分類而已, 在針對不同的Category 內加入Preference object, 可以看到畫面上的"第一個分類", "第二個分類"....均是。


Preference : 更單純, 通常只是被用來當作"說明"功能而已。



稍微簡介了用到的Preference Objects 之後, 開始來看PreferenceActivity.java 程式主要在處理每個Preference object 初始的Summary 設定, 和註冊value change 的事件。



package com.example.preferenceactivity;


import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceScreen;
import android.preference.RingtonePreference;
import android.view.Menu;


public class MainActivity extends PreferenceActivity implements OnPreferenceChangeListener
{
 
 EditTextPreference editTextPreference, secondEditTextPreference;
 ListPreference listPreference;
 RingtonePreference ringtonePreference;
 
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preference); // 記得替換掉預設的 setContentView(R.layout.activity_main) 
    
        editTextPreference = (EditTextPreference) findPreference("EditTextP");
  editTextPreference.setOnPreferenceChangeListener(this);
  //替Preference註冊value改變事件, 因為我們要動態的更新Summary content
  
  editTextPreference.setSummary("EditTextPreference Value:" +editTextPreference.getText());
  
  listPreference = (ListPreference) findPreference("ListP");
  listPreference.setOnPreferenceChangeListener(this);
  listPreference.setSummary("ListPreference Value:"+ listPreference.getEntry());
  
  ringtonePreference = (RingtonePreference) findPreference("RingtoneP");
  ringtonePreference.setOnPreferenceChangeListener(this);
  
  //---In the second Screen
  
  secondEditTextPreference = (EditTextPreference) findPreference("SPCEditTextP");
  secondEditTextPreference.setOnPreferenceChangeListener(this);
  secondEditTextPreference.setSummary("EditTextPreference Value:" +secondEditTextPreference.getText());  
  
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }       

 @Override
 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
   Preference preference)
 {
  // TODO Auto-generated method stub
  return super.onPreferenceTreeClick(preferenceScreen, preference);
 }

 public boolean onPreferenceChange(Preference preference, Object newValue)
 {
  // TODO Auto-generated method stub  
   
  if(preference.getKey().equals("EditTextP")) //根據不同的key來辨別不同的Preference
   preference.setSummary("EditTextPreference Value:" +newValue);
  else if(preference.getKey().equals("ListP"))
   preference.setSummary("ListPreference Value:" +((ListPreference) preference).getEntries()[Integer.parseInt(newValue.toString())]);
      //從Entry中對應顯示使用者選擇的item value
  else if(preference.getKey().equals("RingtoneP"))
   preference.setSummary("RingtonePreference Value:" +newValue);
  else if(preference.getKey().equals("SPCEditTextP"))
   preference.setSummary("EditTextPreference Value:" +newValue);
  
  return true; 
 }
}


最後當我們成功儲存了每個Preference objects value 後, 該透過甚麼方法取得呢? 

網路上建議的作法:

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);

return value preferences 為 context 所使用的preference file, 接下來就可以對 preferences 進行數值的存取了。


相關文章:



2012年9月29日 星期六

[Android] 使用 TextSwitcher 切換ListView的內容 (How to use TextSwitcher change ListView content)



    
    透過TextSwitcher 的切換來改變ListView 的內容, 並且在切換的同時加入Animation 動畫. 然後使用Typeface將TextView的字體改變, 意即使用custom Font. 而為了要使用custom Font需將字體檔 wt021.ttf 事先匯入 assert/font folder, 到時候再透過 Typeface.createFromAsset(getAssets(), "fonts/wt021.ttf") 取用, 特別注意要加上副檔名 .ttf



首先來看 MainActivity的部分,



package com.example.textswitcher;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.ListView;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;

public class MainActivity extends Activity
{

    TextSwitcher TSwitcher;
    static int textIndex = 0;
    
    String[] TSwitcherContent;
    ListView LView;
    TextArrayAdapter LAdapter;
    
    Typeface font;
    
 
 @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        font = Typeface.createFromAsset(getAssets(), "fonts/wt021.ttf");
        //從 assert folder 中取用我們匯入的自訂字體 wt021.ttf  
        
        iniTexTSwitcher();
        iniListView();
    }
    
 private void iniTexTSwitcher()
    {
  // TODO Auto-generated method stub
     
  TSwitcher = (TextSwitcher) findViewById(R.id.textSwitcher);
  
  //透過 setFactory method 產生兩個 Views給 TextSwitcher切換
     TSwitcher.setFactory(new ViewFactory(){
      
      //實作makeView() method 產生 View供TextSwitcher作切換 
      public View makeView()
      {       
       TextView tv =new TextView(MainActivity.this);
       tv.setTypeface(font); // 使用自訂的字體
       tv.setTextSize(40);
       tv.setTextColor(Color.GREEN);       
       tv.setGravity(Gravity.CENTER);           
       return tv;
      }
     });
     
     TSwitcherContent = getResources().getStringArray(R.array.textSwitcherContent);
     //將事先定義在 /res/value 的string array 取出使用
     
     TSwitcher.setText(TSwitcherContent[textIndex]);      
     
     //替 TextSwitcher註冊 onClickListener
     TSwitcher.setOnClickListener(new View.OnClickListener()
     {
      public void onClick(View v)
      {       
       textIndex++;
       
       if (textIndex >= TSwitcherContent.length) textIndex = 0;
         
       TSwitcher.setText(TSwitcherContent[textIndex]);       
       
       LAdapter.notifyDataSetChanged();
       //告知Adapter的data 已經改變了, 所以我們手動的呼叫 notifyDataSetChanged() method
       
       LView.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), android.R.anim.fade_in));
       //當TextSwitcher切換時, 讓ListView有淡入的效果
      }
     });      
      
     TSwitcher.setInAnimation(AnimationUtils.loadAnimation(getApplicationContext(), android.R.anim.slide_in_left));       
     // TextSwitcher切換時也使用動畫效果
 }

 private void iniListView()
    {
  // TODO Auto-generated method stub  
  
  LView = (ListView) findViewById(R.id.listView);
  
  LAdapter = new TextArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1, TSwitcherContent, font);
  //宣告使用custom TextArrayAdapter that extends ArrayAdapter
  //android.R.layout.simple_list_item_1, 為android system resource layout 只能顯示single line, 所以本身即為一個TextView  
  
  LView.setAdapter(LAdapter);  
 }

 
 @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}


接著來談一下TextSwitcher吧, TextSwitcher的父類別為 ViewSwitcher, 所以具有和ViewSwitcher共同的性質, ViewSwitcher 透過 setFactory(new ViewFactory()) 產生兩個child views給ViewSwitcher作切換, ViewSwitcher 只能夠有兩個 child views, 並且一次只能顯示一個, 另外 ViewSwitcher 有兩個子類別, 一為TextSwitcher 另一個為 ImageSwitcher, 差別在於TextSwitcher 所包含的 child views 只能為 TextView type的 view, ImageSwitcher 只能擁有 ImageView type的 child view



處理完畢 MainActivity.java 的內容之後我們來看 ListView, 首先ListView 和 data 之間必須有Adapter作為配接, 在這邊我們使用custom Adapter that extends ArrayAdapter, 我已經將這個custom adapter 取名為 TextArrayAdapter, 並且獨立一個.java 如下


package com.example.textswitcher;

import android.app.Activity;
import android.content.Context;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class TextArrayAdapter extends ArrayAdapter
{

 Activity mainActivity;
 Typeface itemFont;
 
 public TextArrayAdapter(Context context, int textViewResourceId, String[] objects, Typeface font)
 {
  super(context, textViewResourceId, objects);
  // TODO Auto-generated constructor stub
  
  mainActivity = (Activity) context;
  //特別取得 MainActivity
  
  itemFont = font;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent)
 {
  // TODO Auto-generated method stub
  
  View rowview = convertView;
        if (rowview == null) {
                LayoutInflater inflater = mainActivity.getLayoutInflater();
                rowview = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
                //為了要把xml所描述的Layout轉變為View , 也就是我們要的rowview
                //所以必須使用LayoutInflater來轉化 , 而要取得LayoutInflater的Instance方式還包括
                //LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                //接著再使用inflate (int resource, ViewGroup root, boolean attachToRoot)
                //傳回我們要的rowview
        }
        
        TextView item = (TextView) rowview;
        
        item.setTypeface(itemFont);
        item.setGravity(Gravity.CENTER);
        
        //根據當前的 textIndex 來決定ListView該呈現的內容
        switch(MainActivity.textIndex)
        {
         case 0:
          item.setText("First");
         break;
         
         case 1:
          item.setText("Second");
         break;
         
         case 2:
          item.setText("Third");
         break;
         
         case 3:
          item.setText("Fouth");
         break;        
        }
  
  return rowview;
 } 

}



最後是簡單的 activity_main.xml


    
    

    

    






2012年9月27日 星期四

[雜談] Hello, I`m Back !



    

         看了上一篇文章的發文日期在四月份, 發覺荒廢了很久已經快半年沒發新文章了, 這段時間都在寫C 和忙別的雜事, 最近因為工作的關係又重新回到久違的行動平台了, 接到主管交付開發Android app, iOS app的任務, 按慣例要將開發 app學到的新心得整理放上來, 所以之後也會有對於開發iOS心得的相關文章, 讓自己以後可以快速的找查相關資料也方便檢視所學, 最後希望這些心得對app開發者們有幫助, 加快自己進入這個領域和縮短開發app的時間。




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皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....



2012年4月5日 星期四

[TQC+ Android] 3-6 查詢地圖 Use MapView , Google Map API 2.0





設計查詢地圖 , 可輸入想查詢的經緯度 , 於輸入後點選查詢 , 可顯示欲查詢地圖並可放大縮小地圖 , 這題的重點是在申請 Android Maps API Key , 過程是先從開發環境得電腦中取得Keystore 的MD5編碼 , 再透過Google網站簽暑便可取得了 , 另外因為必須使用到Google API 所以在建立Android 專案時 , Build target 記得選擇有Google API。


以下是取得Android Maps API Key的步驟:


1. 至開發環境 Eclipse IDE 中
    Window > Preferences > Android > Build > Default debug keystore 取得 keystore的存放路徑








2. 進入Dos模式 . 將目錄切換至JAVA資料夾下的\jre\bin, 範例路徑: C:\Java\jdk1.6.0\17\jre\bin
   (視電腦安裝路徑而定) , 然後在目錄下輸入以下指令 , 取得MD5編碼。


keytool -list -alias androiddebugkey -keystore "第一步驟的keystore的存放路徑" -storepass android -keypass android






3. 指令執行成功之後 , 會產生 keystore 檔案 , 並可取得一組MD5編碼 , 然後至以下網址輸入MD5編碼 , 取得Android Maps API Key。


https://code.google.com/intl/zh-TW/android/maps-api-signup.html




圖片中最下面那段xml配置範例是之後要加入到MapView的android:apiKey屬性值中的。


接下來我們來看看程式碼的部分。


package COM.TQC.GDD03;


import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle; 
import android.view.View; 
import android.widget.Button; 
import android.widget.EditText; 
import com.google.android.maps.GeoPoint; 
import com.google.android.maps.MapActivity; 
import com.google.android.maps.MapController; 
import com.google.android.maps.MapView; 


public class GDD03 extends MapActivity 
{
  private MapController MapController; 
  private MapView MapView;
  private Button btn_query, btn_zoomIn, btn_zoomOut; 
  private EditText EditLongitude; //經度
  private EditText EditLatitude; //緯度
  private int intZoomLevel=0;
  
  private double dLng=121.228626;//預設經度
  private double dLat=25.0402226; //預設緯度
  

  @Override 
  protected void onCreate(Bundle icicle) 
  { 
    super.onCreate(icicle); 
    setContentView(R.layout.main); 

    EditLongitude = (EditText) this.findViewById(R.id.editText1);
    EditLongitude.setText(dLng+"");
    EditLatitude = (EditText) this.findViewById(R.id.editText2);
    EditLatitude.setText(dLat+"");
    
    intZoomLevel = 15;
    
    MapView = (MapView) findViewById(R.id.mapview);    
    
    //MapView.setBuiltInZoomControls(false);
    //參數為True時 , 使用者可以直接控制MapView的縮放大小 
    //在這邊使用false讓使用者使用我們的按鈕來控制MapView的縮放大小
    
    MapView.setStreetView(true);
    //顯示街道圖
    MapController = MapView.getController();
 refreshMapView();    
    
 btn_query = (Button)findViewById(R.id.btn_query); 
    btn_query.setOnClickListener(new Button.OnClickListener() 
    { 
      //@Override
      public void onClick(View v) 
      {        
        if(EditLongitude.getText().toString().equals("")||
           EditLatitude.getText().toString().equals(""))
        {
          showDialog("經度或緯度填寫不正確!");
        }
        else
        {        
         showDialog("顯示地圖");
         refreshMapView(); 
        }
      } 
    });      
    
    btn_zoomIn = (Button)findViewById(R.id.btn_zoomIn); 
    btn_zoomIn.setOnClickListener(new Button.OnClickListener() 
    { 
      //@Override 
      public void onClick(View v) 
      { 
       intZoomLevel++;
       MapController.setZoom(intZoomLevel);    
      } 
    });      
    
    btn_zoomOut = (Button)findViewById(R.id.btn_zoomOut); 
    btn_zoomOut.setOnClickListener(new Button.OnClickListener() 
    { 
      //@Override
      public void onClick(View v) 
      { 
       intZoomLevel--;    
       MapController.setZoom(intZoomLevel);
      } 
    });
  } 
  
  GeoPoint GPoint;  
  
  public void refreshMapView() 
  {      
     dLng = Double.parseDouble(EditLongitude.getText().toString());
     dLat = Double.parseDouble(EditLatitude.getText().toString());
     GPoint = new GeoPoint((int)(dLat*1E6),(int)(dLng*1E6));
     //GeoPoint GPoint = new GeoPoint(19240000,-99120000);
     MapController.setCenter(GPoint);
     //將MapView的中心點定位在GPoint的位置
     MapController.setZoom(intZoomLevel);
    //設定地圖縮放比例
     MapView.invalidate(); 
  } 
   
  @Override 
  protected boolean isRouteDisplayed() 
  { 
    return false;
    //為了計算目的的需要 , 要讓Server知道我們是否正在顯示相關的路由資訊
    //在這個應用中是用不到 , 所以我們回傳false
  }
    
  private void showDialog(String mess)
  {
    new AlertDialog.Builder(GDD03.this).setTitle("Message")
    .setMessage(mess)
    .setNegativeButton("確定", new DialogInterface.OnClickListener()
    {
      public void onClick(DialogInterface dialog, int which)
      {}
    })
    .show();
  }
} 




由於MapActivity class 不在Android 官方內建的API中 , 我們必須在AndroidManifest.xml中引入 library : com.google.android.maps , 另外我們會存取使用者的當前座標所以還必須註冊相關的 use-permission 。

    
    
    
       
        
        
            
                
                
            
        
          
    
    
 




再來看看layout檔案

    






先前提到MapActivity class 不是Android 官方的API , 所以在官方的開發函式文件中是找不到相關資料的 , 下面這個網站可取得相關的資料。


Google Map APIs




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







2012年4月1日 星期日

[TQC+ Android] 3-5 接聽來電 Use PhoneStateListener , ContentResolver





設計接聽來電程式 , 以程式接收來自DDMS的模擬來電 , 並顯示來電號碼於畫面 , 開啟程式之後就等待來電 , 不過倒不是使用BroadcastReceiver , 而是使用 PhoneStateListener 來監聽來電狀態 , 接到來電之後再以電話號碼搜尋通訊錄 , 找出來電的人是誰 , 以下是程式碼。


package COM.TQC.GDD03;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.widget.TextView;

public class GDD03 extends Activity
{
 
  public TextView myTextView1;

  /** Called when the activity is first created. */
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    myTextView1 = (TextView) this.findViewById(R.id.textView1);
    
    exPhoneCallListener myPhoneStateListener = new exPhoneCallListener();
        
    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    //取得TelephonyManager    
    
    telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    //將電話狀態的Listener加到取得的TelephonyManager中
    //在這裡我們選擇要聆聽的狀態為PhoneStateListener.LISTEN_CALL_STATE
  }  
   
  //運用PhoneStateListener監聽電話狀態  
  class exPhoneCallListener extends PhoneStateListener
  {    
    //PhoneStateListener 中不同的聆聽事件所要override的method各有不同, 請針對之前註冊的聆聽事件相對應的method進行override
 //我們之前選擇的聆聽事件為 LISTEN_CALL_STATE , 所以override onCallStateChanged() method 
 public void onCallStateChanged(int state, String incomingNumber)
    {
     switch (state)
     {
        //電話狀態是閒置的
        case TelephonyManager.CALL_STATE_IDLE:                    
           break;
        
           //電話狀態是接起的       
        case TelephonyManager.CALL_STATE_OFFHOOK:                
           break;
           
           //電話狀態是響起的        
        case TelephonyManager.CALL_STATE_RINGING:           
        getContactPeople(incomingNumber);
        //由電話號碼查詢聯絡人
        break;
        
        default:
        break;
     }
     
      super.onCallStateChanged(state, incomingNumber);
    }
  }

  private void getContactPeople(String incomingNumber)
  {
    myTextView1.setTextColor(Color.BLUE);
    
    ContentResolver contentResolver = getContentResolver();
    //必須靠ContentResolver的協助取得電話簿的資料
    
    Cursor cursor = null;
    
    String[] projection = new String[]
    {PhoneLookup._ID, PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY};
    //在query後我們要求ContentResolver回傳以上欄位的資料內容
    
    Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(incomingNumber));
    //將來電號碼append上去當作篩選條件 , 效果等於WHERE = incomingNumber
    
    cursor = contentResolver.query(uri, projection, PhoneLookup.NUMBER + " = ?", new String[] {incomingNumber}, null);
    /*
     * 因為我們所要查詢手機中通訊錄的資料, 所以使用ContentResolver class 來存取Content model
     * 我們可以將 PhoneLookup.CONTENT_FILTER_URI 這個參數視為想要存取的Table, 你也可以使用 null 他就會回傳所有的column 不過這樣比較沒有效率
     * PhoneLookup.NUMBER + "?" 這個字串相當於SQL語法中的 WHERE , 搭配後面的參數 new String[] {incomingNumber}
     * 其實可以寫成這樣 PhoneLookup.NUMBER = incomingNumber
     * 
     * public final Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
     * 
     * uri : The URI, using the content:// scheme, for the content to retrieve.
     * projection : A list of which columns to return. Passing null will return all columns, which is inefficient.
     * selection A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given URI.
     * selectionArgs You may include ?s in selection, which will be replaced by the values from selectionArgs, in the order that they appear in the selection. The values will be bound as Strings.
     * sortOrder How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered.
     * 
     */  
    
    if (cursor.getCount() == 0) //判斷此電話號碼存不存在電話簿中, 不存在的話使用 unknown Number
    {
      myTextView1.setText("unknown Number:" + incomingNumber);
    } 
    else if (cursor.getCount() > 0) //這來電號碼存在電話簿中 , 顯示聯絡人姓名
    {
      cursor.moveToFirst();      
      
      String name = cursor.getString(1);
      //取得index 1 欄位的字串型態資料, 關於欄位的順序就是當初我們 projection 字串陣列中的字串順序
      //如果當初的參數為 null,那它會回傳所有的column, 這樣最好搭配 getColumnIndex(String ColumnName)來找尋指定column的資料 , 而回傳的資料型態同樣必須查詢文件才能得知
      
      myTextView1.setText(name + ":" + incomingNumber);
    }
  }
}


這個程式的運作流程開頭有大概說明了 , 程式中註解也進一步解釋了 , 在這邊在稍微補充 , PhoneStateListener 可以監控手機裝置的狀態 , 訊號強度 來電狀態 基地台地點....等等不少事件, , PhoneStateListener 中不同的監控事件所要override的method各有不同, 請針對之前註冊的監控事件相對應的method進行override , 如果想查詢監控事件所對應到的method可以造訪這個文件 : PhoneStateListener 




接下來是AndroidManifest.xml , 別忘了因為我們要監控來電的狀態所以要註冊 <uses-permission android:name="android.permission.READ_PHONE_STATE"/> 另外讀取通訊錄也需要註冊權限 : <uses-permission android:name="android.permission.READ_CONTACTS"/>
    
    
    
        
            
                
                
            
        
    
    
 















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







[TQC+ Android] 3-4 接收簡訊 Use SmsMessage , BroadcastReceiver





設計接收簡訊程式 , 以DDMS模擬產生一則簡訊 , 簡訊送達時使用Toast 顯示傳送者電話及簡訊內容 , 這題實作的技巧其實在之前的文章 [TQC+ Android] 3-2 接收SMS Use BroadcastReceiver, SmsMessage, onNewIntent 都講完了 , 而且這題只是單純的顯示接收到的簡訊內容 , 記得註冊user-permission 和 "android.provider.Telephony.SMS_RECEIVED" , 相當的簡單 , 以下是程式碼。


package COM.TQC.GDD03;

import android.app.Activity; 
import android.os.Bundle; 
import android.widget.TextView; 

public class GDD03 extends Activity 
{ 
  private TextView mTextView1; 
  /** Called when the activity is first created. */ 
  @Override 
  public void onCreate(Bundle savedInstanceState) 
  { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 
     
    mTextView1 = (TextView) findViewById(R.id.myTextView1); 
    mTextView1.setText("等待接收簡訊..."); 
  }
}
這是一個等待簡訊進來的 main Activity。






接下來是 GDD03_SM_Receiver.java , 是用來等待簡訊事件的


package COM.TQC.GDD03 ;

import java.util.Set;

import android.content.BroadcastReceiver; 
import android.content.Context; 
import android.content.Intent; 
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;



public class GDD03_SM_Receiver extends BroadcastReceiver 
{ 
 
  @Override 
  public void onReceive(Context context, Intent intent) 
  { 
    // TODO Auto-generated method stub 
    
    Toast t = Toast.makeText(context, "", Toast.LENGTH_LONG);
   
 if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED"))
    {     
  Bundle bundle = intent.getExtras();
  
  //檢查一下返回的Bundle裡面有哪些的資訊, 檢查Key
  Set KeySet = bundle.keySet();
     
     for(String s : KeySet)
     {
        Log.d("We have key elements", "Key:"+s);
     }
          
  Object messages[] = (Object[]) bundle.get("pdus");
  SmsMessage[] smsMessage = new SmsMessage[messages.length];
  for (int n = 0; n < messages.length; n++)
  {
   smsMessage[n] = SmsMessage.createFromPdu((byte[]) messages[n]);
      //將原始的PDU格式的資料轉為smsMessage的格式, 由於傳入的簡訊由於長度的限制可能不止一封
  }
  
     t.setText("接收到來自:"+smsMessage[0].getOriginatingAddress()+"\n----傳來的簡訊----\n"+smsMessage[0].getDisplayMessageBody());
     t.show();
    }
    else
    {
     t.setText(intent.getAction());
     t.show();
    }   
  } 
} 






最後別忘了 AndroidManifest.xml

    
    
        
            
                
                
            
        
        
            
                
            
        
    
    
 






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



Google Analytics