2012년 3월 1일 목요일

수신 및 발신 전화번호 알아내기

오늘은 수신 및 발신 전화번호를 알아내 보도록 하겠습니다.

1. 수신 전화번호 알아내기
  • 두가지 방법이 있습니다. 두가지 방법다 Manifest.xml에 다음의 permission이 등록되어 있어야 합니다.
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>


A. TelephonyManager 및 PhoneStateListener 이용하기
  • Activity나 Service에서 간단한 코드로 이를 알아 낼 수 있습니다.
  • 우선, onCreate() 에서 TelephonyManager를 얻어냅니다.
TelephonyManager telephonyMgr = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
TelephonyManager  telephonyMgr.listen(new PhoneStateListener(){

            public void onCallStateChanged(int state, String incommingNumber){

                   switch(state) {
                   case TelephonyManager.CALL_STATE_RINGING:
                           Log.d(TAG, " CALL_STATE_RINGING.:" + incommingNumber);
                           break;
 
                    case TelephonyManager.CALL_STATE_IDLE:
                           Log.d(TAG,"CALL_STATE_IDLE ");
                           break;

                   case TelephonyManager.CALL_STATE_OFFHOOK:
                            Log.d(TAG,"CALL_STATE_OFFHOOK ");
                            break;
                   }
             }
}, PhoneStateListener.LISTEN_CALL_STATE);

  • PhoneStateListener는 Class 형태 이므로, onCallStateChanged()를 override하시면 됩니다.
  • incommingNumber안에 String형태로 넘어오기에 구현하기는 너무 간단합니다.

B. BroadcastReceiver 이용하기
  • Manifest.xml에 BroadcastReceiver를 등록합니다.
       <receiver android:name="my.test.CallStateListener">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE"/>            </intent-filter>
        </receiver>
  • 이제 CallStateListener를 구현합니다.
public class CallStateListener extends BroadcastReceiver {
      
       public String TAG = getClass().getSimpleName();

       @Override
       public void onReceive(Context context, Intent received) {

             String action = received.getAction();
             Bundle bundle = received.getExtras();

             if(action.equals("android.intent.action.PHONE_STATE")){

                    String state = bundle.getString(TelephonyManager.EXTRA_STATE);

                    if(state.equals(TelephonyManager.EXTRA_STATE_IDLE)){
 
                             Log.d(TAG, " EXTRA_STATE_IDLE ");
 
                    }else if(state.equals(TelephonyManager.EXTRA_STATE_RINGING)){
 
                             Log.d(TAG, " EXTRA_STATE_RINGING INCOMMING NUMBER : " + bundle.getString(TelephonyManager.EXTRA_INCOMING_NUMBER)); 
                    }else if(state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
 
                             Log.d(TAG, " EXTRA_STATE_OFFHOOK ");
                    }
             }
       }
}
  • 실제 폰을 가지고 위 코드를 실행해 보시면
  • RINGING -> OFFHOOK -> IDLE 순으로 status가 변하는 것을 볼 수 있으며, RINGING 에서만 number를 얻을 수 있습니다.
2. 발신 전화번호 알아내기
  • 발신번호는 TelephonyManager 및 PhoneStateListener를 이용해서는 얻을 수 없습니다.
  • 위의 B. 형태인 BroadcastReceiver를 이용해야 합니다.
  • Manifest.xml에 다음의 permission이 등록되어 있어야 합니다.
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>

  • 위에서 만들어 놓았던 BroadcastReceiver를 다시 이용합니다.
<receiver android:name="my.test.CallStateListener">
              <intent-filter>
                        <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>                        
                        <action android:name="android.intent.action.PHONE_STATE"/>             
              </intent-filter>
</receiver>

  • 이제 CallStateListener에 구현합니다.
public class CallStateListener extends BroadcastReceiver {

      public String TAG = getClass().getSimpleName();

      @Override
       public void onReceive(Context context, Intent received) {

             String action = received.getAction();
             Bundle bundle = received.getExtras();

             if(action.equals("android.intent.action.PHONE_STATE")){

                      String state = bundle.getString(TelephonyManager.EXTRA_STATE);

                      if(state.equals(TelephonyManager.EXTRA_STATE_IDLE)){

                              Log.d(TAG, " EXTRA_STATE_IDLE ");

                       }else if(state.equals(TelephonyManager.EXTRA_STATE_RINGING)){

                              Log.d(TAG, " EXTRA_STATE_RINGING INCOMMING NUMBER : " + bundle.getString(TelephonyManager.EXTRA_INCOMING_NUMBER));
                        }else if(state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){

                              Log.d(TAG, " EXTRA_STATE_OFFHOOK ");
                        }
                }else if(action.equals(Intent.ACTION_NEW_OUTGOING_CALL)){

                        Log.d(TAG, " OUTGOING CALL : " + bundle.getString(Intent.EXTRA_PHONE_NUMBER));
                }
         }
}

  • 빨간색으로 표시된 부분입니다.
  • 위 로그를 보자면  OUTGOING_CALL -> OFFHOOK -> IDLE 의 순서로 status가 바뀝니다.
  • 그리 어렵지는 않지요?

2012년 2월 26일 일요일

AppWidget Update 주기에 대하여

AppWidget의 update 주기에 대해 말씀드리겠습니다.

update 주기를 설정하는 방법은 여러가지가 있겠으나, 대략 다음중 하나를 선택하실 겁니다.

1. appwidget-provider XML 에서 updatePeriodMillis="0"
2. appwidget-provider XML 에서 updatePeriodMillis="원하는 시간만큼"
3. 아니면 프로그램 내에서 직접 AlarmManager를 통해

1. updatePeriodMillis="0"의 의미
- 이렇게 setting하면 3번과 같은 의미를 같습니다. onUpdate()는 프로그램에서 알아서 해야 하다는 말 입니다.
- 프로그램내에서 AppWidget 실행시 한번만 onUpdate()를 call 합니다.
- 이 후에 한번도 onUpdate()를 call하지 않으면, 상당한 시간이 흐른 후에 AppWidget에 setting해 놓은 기능들은, 특히 pending intent는 사라지게 되며 AppWidget은 기능을 제대로 작동되지 않습니다. 꼭 명심하세요.

2. updatePeriodMillis="원하는 시간만큼"
- 원하는 시간에 System에서 onUpdate() call 하게 되지만, 그 주기는 확신할 수는 없습니다.
- 최소시간은 30분입니다. 단위가 millisecond이니 30분 x 60초 x 1000=1800000 가 되겠습니다.
- 아무리 짧게 주어도 효과는 없습니다.

3. AlarmManager를 통해
- 2번 처럼 원하는 시간을 정할 수는 있을겁니다.
- 너무 짧은 시간을 주게 되면, 아마도 Failed Binder Transaction!!! 을 만나게 됩니다.
- AppWidget은 참 편하고 강력하며 iphone과 차별화된 기능이지만, 제가 보기에는 상당히 무겁습니다.
- 짧은 시간에 화면을 update하는 것은 금물입니다.

2012년 2월 19일 일요일

AppWidget 구현시 가장 중요한 것은?

AppWidget 구현 시 XXXConfigure, XXXProvider, Service 정말 많은 것을 구현해야 합니다.
그중에서도 Widget의 구성요소를 사용자로하여금 설정할 수 있게 해 주는 것이 XXXConfigure 인데요..

XXXConfigure를 만드는 순간 우리는 유령 Widget에 시달려야 합니다.

XXXConfigure는 Activity형태라는 것 다 아실테고..
User가 Activity를 빠져나가는 방법은 여러가지가 있을 수 있습니다.

1. home 버튼
2. back 버튼
3. 취소버튼 - 친절하게 XXXConfigure 화면에서 취소버튼을 만들은 경우
4. 그외 상상에 맞기겠습니다.

메뉴얼이나 developer site에 가서 보면

XXXConfigure내에서 Widget 만들기를 취소하는 것은 XXXConfigure에 setResult()에 RESULT_CANCELED를 setting하는 것으로 되어 있으나...아무리 이렇게 해봐도 유령은 계속 계속 생기게 됩니다.

현재 얼마나 많은 appWidget이 host에 등록 되어 있는지 대한 코드는 다음과 같습니다.

ComponentName compName = new ComponentName(this, XXXProvider.class);
int appWidgetIds[] = AppWidgetManager.getInstance(this).getAppWidgetIds(compName);
AppWidgetHost host = new AppWidgetHost(this, 0);

for(int i = 0; i < appWidgetIds.length; i++){
     Log.d(TAG, "[" + i + "] " + appWidgetIds[i]);
}

이코드를 XXXConfigure onCreate()에 넣어 보시면 현재 얼마나 많은 위젯들이 있는지 알 수 있겠죠.

유령은 XXXConfigure를 실행하고 setResult(RESULT_OK, intent); 가 호출되지 않은 상태인데요, 화면에 위젯이 표시되지 않고 host에 id로만 존재하는 놈을 말합니다.

그럼 어떻게 지울 수 있을까요....?

항상, onPause() 아니면 onDestroy()에서 (onPause()가 나을 것 같아요..) 유령을 찾아 지워야 합니다.

지우는 코드는 위에서 언급한 widgetId를 보는 코드와 동일합니다.

ComponentName compName = new ComponentName(this, XXXProvider.class);
int appWidgetIds[] =AppWidgetManager.getInstance(this).getAppWidgetIds(compName);
AppWidgetHost host = new AppWidgetHost(this, 0);

for(int i = 0; i < appWidgetIds.length; i++){
        Log.d(TAG, "[" + i + "] " + appWidgetIds[i]);
        if(appWidgetIds[i]가 유령입니까?){
              host.deleteAppWidgetId(appWidgetIds[i]);
        }else{
              // do nothing..
        }
}

빨간색으로 칠한 부분을 생각해 보겠습니다.

화면에 widget이 생성되어 있는지 아닌지 알아내는 방법을 못찾았습니다.(좀 알려주세요.)

그걸 안다면 이런짓을 하지는 않을 겁니다. 그러나 방법을 모르기에 제대로 생성된(즉, User가 XXXConfigure화면에서 OK버튼을 눌렀을때) widget과 아닌것을 구분하는 구분자 정도는 widget 생성시 만들어서 어디에 저장해 놓아야 합니다. 가장 편리하고 쉬운 것은 SharedPreferences를 이용하는 것입니다.

즉, 제대로된 생성된 appWidget의 경우, SharedPreferences 에 제대로 된놈인가를 표시하는 것이죠....

appWidgetIds[i] 가 유령인지 아닌지 판단시 SharedPreferences에 등록이 되어 있는지를 체크 하는 것입니다.

유령체크코드는 application마다 다르기에 여기서는 언급은 하지 않지만요...

조금은 지저분하고 googling을 해봐도 딱히 유령을 가장 깔끔하게 지우는 방법을 찾지는 못했습니다.

암튼 이만.... 댓글 달아 주시면 답변 드리겠습니다.



2012년 1월 11일 수요일

AppWidget에서 사용할 수 있는것은?

AppWidget에서 사용할 수 있는 Layout
FrameLayout,
LinearLayout
RelativeLayout.

AppWidget에서 사용할 수 있는 Widget들..
AnalogClock,
Button,
Chromometer,
ImageButton,
ImageView,
ProgressBar
TextView

너무 없죠.. 수수께끼 하는 것도 아니고..
그래도 만들어 볼만한게 AppWidget입니다.

그러나 3.x, 4.x로 넘어가면서 사용할 수 있는게 더 많아 졌다니...

실행중인 Task별 Activity 보기..

1. AndroidManifest.xml에 Permission 처리
    -  <uses-permission android:name="android.permission.GET_TASKS"/>

2. 다음과 같이 코딩을 하세요..
  
    // ActivityManager를 얻고..
   ActivityManager actMgr = (ActivityManager)AppPinScreenLockActivity.this.getSystemService(Activity.ACTIVITY_SERVICE);
   
   
    List<RunningTaskInfo> info = actMgr.getRunningTasks(7);
   
    for (Iterator<RunningTaskInfo> iterator = info.iterator(); iterator.hasNext();)  {
         RunningTaskInfo runningTaskInfo = (RunningTaskInfo) iterator.next();
         Log.d(TAG, "" + runningTaskInfo.topActivity.getClassName());
    }

2012년 1월 7일 토요일

Implementing Dynamic Tab Widget

1. layout xml file (main.xml) 만들기

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost" 
    android:layout_height="fill_parent"
    android:layout_width="fill_parent">
     <LinearLayout
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:orientation="vertical"
         android:padding="5dp">

         <TabWidget
             android:id="@android:id/tabs"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"/>
          <FrameLayout
             android:id="@android:id/tabcontent"             android:layout_height="wrap_content"
             android:layout_width="fill_parent"
             android:padding="5dp"/>
    </LinearLayout>
</TabHost>
- xml의 계층을 건들지 마시고 각 id의 name은 미리 android에 선언된 이름이므로 바꾸지 말아야 한다.

2. MainActivity 만들기
    - Tab을 만들기 위해서는 우선 TabActivity를 상속받은 Activity를 만들고,  TabHost.TabContentFactory 를 implement해야 한다.

public class TabTestActivity extends TabActivity implements TabHost.TabContentFactory {
          
             .......
       @Override
       public void onCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState);
           // 위에서 만든 main.xml           setContentView(R.layout.main);
           // main.xml 내에 있는 @android:id/tabhost 
           TabHost tabHost = getTabHost();    
       
           // 1. 새로운 tab생성. 물론 생성되는 모든 TabSpec들은 @android:id/tabs 내에 포함되게 됨
           TabHost.TabSpec tabSpec = tabHost.newTabSpec(getString(R.string.str_appranker_tab1));
           // 2. 이부분에서 단순히 String만을 주게 되면 Tab Widget에 String 만 표시된다.

           // 만약, 더 다양한 효과를 주고 싶으면 이부분에 View를 넘기면 된다.
           tabSpec.setIndicator(getString(R.string.str_appranker_tab1));
           // 3. 이부분이 상단 Tab을 눌렀을때 하단에 표시되는 내용부분이다.
           // 직접, View를 넘길수 없으며 Dynamic하게 생성되는 View들을 FrameLayout내에 넣어야 하기에 TabContentFactory를 이용하게 된다.
            tabSpec.setContent(this);
            // 4. TabHost에 생성한 Tab을 넣는다.
            // Tab을 더 생성하고 싶으면 1~4 다시 수행.            tabHost.addTab(tabSpec);
       }

       @Override
       public View createTabContent(String tag) {

           LayoutInflater inflater = getLayoutInflater();
           // Tab을 구별할 수 있는 tag 값에 따라 다른 View를 R.layout.content inflate해서 넣으면 되며, 가장 중요한 것은 @android:id/tabcontent의 하위 view로 rootview를 정의해야한다는것..
           // 그래야 나중에 framelayout에서 보이고 안보이고를 결정해 주죠......
           if(tag.equals(getString(R.string.str_tab1))){
                     return inflater.inflate(R.layout.content, getTabHost().getTabContentView()); 
           }
           return null;
       }
}


-- 근데요.. 하나의 activity내에서 FrameLayout에서 보여주게될 복잡한 view들을 control한다는것은 조금 힘들고 지저분해 보이네요...
-- tabSpec.setContent()에 Intent를 넣어 Activity가 실행되게하여 MVC모델에 충실하게 만드는 것은 어떨런지.....

2012년 1월 2일 월요일

intent-filter의 Grouping..?

안드로이드가 배우기 힘든것은 될것 같은데 안되는 것들이 있고, 이를 극복하기 위해서 참고할 만한 글을 찾기에 너무 힘들고.... 내가 직접 bug-around 해야하고...
안드로이드 개발자에게 직접 묻기도 뭐하고...

암튼, 오늘 찾아 낸 거...

intent-filter도 grouping이 필요하다는 것.. (나만 몰랐나?, 원래 해야하나? 누구든 알면 알려줘여..)

PACKAGE_ 관련 action과 BOOT_COMPLETED, USER_PRESENT는 다른 group으로 묶어야 제대로 동작을 합니다.

말하자면 아래와 같이.... 참고 하세요..

       <receiver android:name=".XXXBootLauncher">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED"/>
                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
                <data android:scheme="package" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.USER_PRESENT"/>
            </intent-filter>
        </receiver>

2012년 1월 1일 일요일

Package 삭제 추가에서 PackageName알아내기

등록된 BroadcastReceiver로 Package의 추가 삭제 intent가 전달되는 데요..
packagename은 Uri형태로 전달 됩니다.

즉, package:com.helloaxl.xxx 형태인데요...

이때 packageName을 정확히 얻어 올려면,

intent.getData().getSchemeSpecificPart() 를 호출해야 com.helloaxl.xxx를 얻어 올수 있답니다.

Package 추가 삭제 알아내기

 Manifest 파일에 다음과 같이 receiver를 등록합니다.

<receiver android:name=".XXXReceiver">
   <intent-filter>
     <action android:name="android.intent.action.PACKAGE_ADDED" />
     <action android:name="android.intent.action.PACKAGE_REMOVED" />
     <data android:scheme="package" />
  </intent-filter>
</receiver>

여기서 가장 중요한것은 <data android:scheme="package" />...
빼먹지 마시라는거...