'3. Implementation/Android'에 해당되는 글 51건

  1. 2011.07.28 시스템 서비스에 접근하기
  2. 2011.07.21 apk 및 .jar 파일을 재수정하여 패키징하는 방법
  3. 2011.07.21 실행중인 Task 리스트 얻기
  4. 2011.07.19 [펌] 하나의 프로세스에서 여러 애플리케이션 실행하기
  5. 2011.07.19 [펌] 안드로이드 소스 빌드시 일부만 빌드하기 - mmm 명령
  6. 2011.06.16 Android 프레임워크에서 사용하는 uid 의 종류
  7. 2011.03.25 ADB 디버깅 로그
  8. 2011.03.09 성능 벤치마킹 App
  9. 2011.03.07 How to discover memory usage of my application in Android
  10. 2011.02.21 stdout 와 stderr 출력 보기
  11. 2011.02.21 Wireless Adb Debugging (TCP 를 이용한 adb 디버깅) - Rooting Only 3
  12. 2011.02.21 안드로이드 CPU Usage & Memory Usage 가져오기
  13. 2011.02.14 Droid Explorer
  14. 2011.02.10 Security Permissions in Android
  15. 2011.01.27 Gingerbread NDK Awesomeness
  16. 2010.12.08 실행가능한 애플리케이션 목록 가져오기
  17. 2010.10.09 [펌] 소스 #3 - Dialog 없이 wheel 만 있는 ProgressBar 만들고 WebView 위에서 사용하기
  18. 2010.08.16 Local Service and Remote Service ( AIDL Service ) 3
  19. 2010.08.15 HttpClient 및 URL 테스트
  20. 2010.08.15 ContentProvider Example
2011. 7. 28. 03:02

시스템 서비스에 접근하기

사용가능한 서비스 리스트 출력 예

android.os.IServiceManager sm = android.os.ServiceManagerNative.getDefault();
try {
	String[] services = sm.listServices();	
	for (int i = 0; i < services.length; i++) {
		Log.i("Services", i + " : " + services[i]);
	}
} catch (DeadObjectException e) {
	Log.i("Services", e.getMessage(), e);
}
아래는 애플리케이션에서 이용가능한 API 목록입니다. 단, 인터페이스에 접근할 수 있다고 해서 사용가능한 거랑은 다릅니다. 왜냐면 적절한 권한이 요구될 수 있기 때문입니다.

또한 출처 워낙 글이 옛날(2007. 12. 14)꺼라 인터페이스 이름이 변경된 서비스들이 있을 수 있습니다.

Window
android.view.IWindowManager windowService 
= android.view.IWindowManager.Stub.asInterface(sm.getService("window"));

Package
android.content.IPackageManager packageService 
= android.content.IPackageManager.Stub.asInterface(sm.getService("package"));

Power
android.os.IPowerManager powerService 
= android.os.IPowerManager.Stub.asInterface(sm.getService("power"));

Statistics
android.os.IStatisticsService statisticsService 
= android.os.IStatisticsService.Stub.asInterface(sm.getService("statistics")); 

Activity
android.app.IActivityManager activityService 
= android.app.IActivityManager.Stub.asInterface(sm.getService("activity"));
 
USB
android.os.IUsb usbService 
= android.os.IUsb.Stub.asInterface(sm.getService("USB"));

Alarm
android.app.IAlarmManager alarmService 
= android.app.IAlarmManager.Stub.asInterface(sm.getService("alarm"));

Content
android.content.IContentService contentService 
= android.content.IContentService.Stub.asInterface(sm.getService("content"));

Location
android.location.ILocationManager locationService 
= android.location.ILocationManager.Stub.asInterface(sm.getService("location"));

Notification
android.app.INotificationManager notificationService 
= android.app.INotificationManager.Stub.asInterface(sm.getService("notification"));

Bluetooth
org.bluez.IBluetoothService bluetoothService 
= org.bluez.IBluetoothService.Stub.asInterface(sm.getService("org.bluez.bluetooth_service"));

Phone
android.telephony.IPhone phoneService 
= android.telephony.IPhone.Stub.asInterface(sm.getService("phone"));

StatusBar
android.server.status.IStatusBar statusbarService 
= android.server.status.IStatusBar.Stub.asInterface(sm.getService("statusbar"));

Vibrator
android.os.IVibratorService vibratorService 
= android.os.IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));

Volume
android.view.IRingerVolume volumeService 
= android.view.RingerVolumeNative.asInterface(sm.getService("volume"));
출처: 
http://davanum.wordpress.com/2007/12/14/android-accessing-the-system-services/

관련글: 
http://codemuri.tistory.com/entry/번역-안드로이드에서-숨겨진-시스템-서비스에-접근하기
2011. 7. 21. 05:55

apk 및 .jar 파일을 재수정하여 패키징하는 방법

Smali (스말리), baksmali (박스말리) 를 이용하면 기 작성된 apk 또는 jar 파일의 일부를 수정한 후 다시 리패키징 할 수 있습니다. 예를 들어, AngryBird.apk 파일을 추출한 후 일부 악성 코드를 추가한 후 다시 리패키징하면 이 ak 파일을 설치한 분들은 의도하지 않은 코드가 실행되게 되는거죠. 

물론 이 의도하지 않은 코드가 실행되기까지는 권한등과 같은 안드로이드의 여러가지 보안 기법이 적용되어 보호될 수도 있지만 루팅된 폰 (특히, super user 권한이 있는)은 특히 위험하게 되는 것입니다.

하지만 이 기법은 안드로이드 테스팅 쪽에서는 훌륭하게 사용될 수 있습니다. 기술이란 어떻게 쓰이느냐에 따라 동전의 양면과 같습니다.

참고 사이트는 아래와 같습니다.


동영상 링크





 
2011. 7. 21. 05:28

실행중인 Task 리스트 얻기

출처: http://shadowxx.egloos.com/10489531

아래 코드 참고

 
ActivityManager activityManager = (ActivityManager) mContext.getSystemService(
    mContext.ACTIVITY_SERVICE);
List<RunningTaskInfo> info;
info = activityManager.getRunningTasks(7);
for (Iterator iterator = info.iterator(); iterator.hasNext();)  {
  RunningTaskInfo runningTaskInfo = (RunningTaskInfo) iterator.next();
  // Do something ...
}


위에서 getRunningTasks 이 파라미터는 maxNum 인데, 이 값을 1을 주면 화면 top 에 있는 태스크를 알 수 있습니다. 이 태스크라 함은 패키지명과 동일합니다. 실행 가능한 패키지 명 가져오기는 아래 링크를 참고하면 됩니다.

http://codemuri.tistory.com/entry/%EC%8B%A4%ED%96%89%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%AA%A9%EB%A1%9D-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0

 
2011. 7. 19. 03:46

[펌] 하나의 프로세스에서 여러 애플리케이션 실행하기

츨처: http://www.androidpub.com/10481
글쓴이: 회색(http://graynote.tistory.com
)

안드로이드는 백그라운드와 포어그라운드에서 여러 애플리케이션(Application)이 상호 연동해서 동작할 수 있게 구성되어있습니다. 문제는 어플리케이션 프로세스(Process)가 너무 자주 그리고 많이 생성되는 것은 메모리와 CPU측면에서 좋지 않습니다. 상호 연동되는 어플리케이션을 여러개 만들어서 동작시키는 것이 필요한 경우에 하나의 프로세스에서 동작하도록 지정할 수 있습니다. 이것게 처리하는 것은 매우 바람직합니다.

1. AndroidManifest.xml에 android:sharedUserId를 지정합니다. 안드로이드는 어플리케이션 마다 리눅스 User ID를 할당하는데 sharedUserId는 서로 다른 어플리케이션에서 같은 User ID를 공유할 수 있도록 합니다. 같은 User ID를 공유하면 서로 파일등의 데이터를 공유할 수 있고 프로세스도 공유할 수 있게 됩니다. 어플리케이션은 같은 Certificate로 서명되어야만 User ID를 공유할 수 있습니다.
1.<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2.package="com.androidpub.sample"
3.android:sharedUserId="androidpub.user"
4.android:versionCode="1"
5.android:versionName="1.0">

2. 각 애플리케이션의 Application 태그에 android:process를 같은 값으로 지정합니다. 
1.<application android:icon="@drawable/icon" android:label="@string/app_name"
2.android:process="androidpub.process.share">

유의해야할 점은 Application Process를 공유함으로써 Application Context도 공유된다는 점입니다. 
그리고 어플리케이션 업데이트시에 User ID가 바뀌는 경우 기존 데이터가 리셋되는것 같습니다. 이미 퍼블리쉬된 어플의 경우 치명적일수도 있으니 주의하셔야 합니다. 

레퍼런스 : http://developer.android.com/guide/topics/manifest/manifest-intro.html






2011. 7. 19. 03:05

[펌] 안드로이드 소스 빌드시 일부만 빌드하기 - mmm 명령

출처: http://www.androidpub.com/3715
글쓴이: 회색(http://graynote.tistory.com)

소스가 있는 위치에서 하기 명령 실행

$ . build/envsetup.sh 

(위 명령이 안되면: $ source ./build/envsetup.sh)

이후,

$ mmm packages/apps/Contacts

와 같이 Contacts 부분만 다시 빌드됩니다. Android.mk 가 존재하는 Path만 적용할 수 있습니다. 

$ make snod

 를 실행하면 위에서 빌드한 Contacts 를 적용한 system.img 를 다시 빠르게 생성합니다.

그리고 한글등이 들어갈 때 utf8 인코딩을 쓰게되는 데 안드로이드 플랫폼 소스 빌드시에 ajvac 가 ascii 인코딩으로 설정되어 있습니다.

build/core/definitions.mk 에서 -encoding ascii 를 -encoding utf8 로 바꿔주면 됩니다. utf8 소스도 사용할 수 있습니다.

그리고 mmm 과 별도로

$make sdk

를 통해 SDK 를 만들 수 있습니다...
 
2011. 6. 16. 22:08

Android 프레임워크에서 사용하는 uid 의 종류

PackageManagerService.Java 에 보면 다음의 코드가 나옵니다.

    public PackageManagerService(Context context, boolean factoryTest) {
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());

        if (mSdkVersion <= 0) {
            Slog.w(TAG, "**** ro.build.version.sdk not set!");
        }

        mContext = context;
        mFactoryTest = factoryTest;
        mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
        mMetrics = new DisplayMetrics();
        mSettings = new Settings();
        mSettings.addSharedUserLP("android.uid.system",
                Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLP("android.uid.phone",
                MULTIPLE_APPLICATION_UIDS
                        ? RADIO_UID : FIRST_APPLICATION_UID,
                ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLP("android.uid.log",
                MULTIPLE_APPLICATION_UIDS
                        ? LOG_UID : FIRST_APPLICATION_UID,
                ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLP("android.uid.nfc",
                MULTIPLE_APPLICATION_UIDS
                        ? NFC_UID : FIRST_APPLICATION_UID,
                ApplicationInfo.FLAG_SYSTEM);

        // 중략 ...


"android.uid.system"
"android.uid.phone"
"android.uid.log"
"android.uid.nfc"

등이 있네요. 위의 uid 와 같은 키로 signing 을 하게 되면 동일한 권한을 가지게 됩니다.
2011. 3. 25. 01:25

ADB 디버깅 로그

안드로이드의 ADB 동작 원리를 파악하기 위해 윈도우즈에서 빌드를 하였습니다. MinGW 를 이용했고, 이클립스에서 디버깅되도록 세팅을 했습니다. 

헌데 막상 디버깅을 해보니 문제점이 있었습니다. 정작 알고자 하는 기능은  fork 되는 자식 프로세스에서 하게 되어 있는데 윈도우즈에서는 gdb 로 fork 된 프로세스를 디버깅할 수 없었습니다. 

linux 에서는 몇가지 옵션을 설정하면 되는 것 같았는데, 윈도우즈에서는 (제가 몰라서인지...) 가능하지 않더군요.

그동안의 삽질을 뒤로하고 다른 일을 하다가...

다시 adb 의 동작 원리를 분석해야할 때가 왔습니다. 다음 3가지 사항을 고려해야 하는데...

1. 진입점에 키 입력을 받을 때까지 기다리도록 하여 fork시  정지시켜 놓고 attach 를 하느냐...
2. adb start-server 를 입력하면 fork 를 하는가? -> 하지 않는다면 그걸로 ㅇㅋ
3. 디버깅을 하기 위해 linux 에서 빌드하느냐... 시간이...ㄷㄷ

이 중에 어떻게 되려나요~

업데이트!

결국 윈도우즈에서 디버깅하는 방법은 성공하지 못했습니다. 그것보다 더 쉬운 방법을 찾았습니다. 제가 알고자 했던 내용은 adb 데몬과 adb host 간의 통신 프로토콜 이었습니다. 이는 사실 adb 를 tcp 를 이용하여 연결한 후 Wireshark 와 같은 도구로  모니터링을 해보면 간단한 문제였습니다. 

캡처된 패킷과 adb 소스, ddmlib 소스등을 뒤적거려 보니 궁금했던 점이 모두 해결되더군요. 혹시 저와 같이 adbd 와 adb host 간의 프로토콜을 파악하시려는 분은 패킷 분석을 이용하시면 보다 쉽게 이해하실 수 있으실 겁니다.

 
2011. 3. 9. 22:33

성능 벤치마킹 App

2011. 3. 7. 22:45

How to discover memory usage of my application in Android

2011. 2. 21. 23:59

stdout 와 stderr 출력 보기

안드로이드에서는 Log 를 찍기 위해서, 기존에 사용하던 System.out 대신에 Log 클래스를 사용하도록 권장하고 있습니다. 하지만 기 구현된 컴포넌트를 재활용하거나 app_process 로 실행하는 등의 특별한 경우에는 Log 클래스 대신 System.out 을 사용해야 하는 경우가 반드시 있습니다.

이때, 출력을 redirection 하는 방법이 구글 검색을 하면 자주 발견됩니다. 저도 그렇게 해서 지금까지 사용해 왔구요. (http://tech.chitgoks.com/2008/03/17/android-showing-systemout-messages-to-console/)

한데, 새로운 사실을 알았습니다. System.out 의 로그를 보는 방법이 정말 간단합니다. (최근 SDK 에 업데이트 되었을까요? eclair 이후에는 adb 페이지를 본적이 없어서 말이죠 .... 끙...)


에 "Viewing stdout and stderr" 을 보면 나와 있습니다.

번역하면 다음과 같습니다. 

 디폴트로, 안드로이드 시스템은 stdout (System.out) 와 stderr(System.err) 의 출력을 /dev/null 로 전송한다. Dalvi VM 을 실행하는 프로세스 내에서는, 시스템의 출력을 로그 파일에 전송하도록 할 수 있다. 이 경우, 시스템은 해당 메시지를 I(영문) 우선순위의 stdout 과 stderr  태그를 가진 로그를 전송한다. (아래 그림에서 System.out 이라고 찍힌 부분)

이런식으로 출력을 재지정하기 위해서는, 실행중인 에뮬레이터 또는 장치를 멈추고, setprop 명령을 이용하여 출력 재지정을 활성화 해야 한다. 

$ adb shell stop
$ adb shell setprop log.redirect-stdio true
$ adb shell start


시스템은 에뮬레이터 또는 장치가 종료될 때까지 이 설정을 유지한다. 에뮬레이터 또는 장치에 디폴트로 설정하기 위해서는, /data/local.prop 에 이 값을 추가할 수 있다.


/data/local.prop 에 관한 내용은 아래 링크 참고:

!!! 저게 항상 찍히는데 확인 필요~


2011. 2. 21. 22:09

Wireless Adb Debugging (TCP 를 이용한 adb 디버깅) - Rooting Only

일반 개발자들은 주로 USB 를 이용하여 adb 를 사용합니다. 하지만 TCP 를 이용하여 adb 를 이용한 디버깅을 해본 사람들은 많지 않을 것입니다. 그도 그럴 것이 Rooting 또는 Engineering 빌드 버전이 아니면 TCP 설정이 적용되지가 않기 때문에 실 장치를 가지고 테스트를 해보기가 쉽지 않겠죠.

이 글을 읽고 계신 분들은 Rooting 폰 또는 Engineering 빌드 버전의 장치를 가지고 있다고 가정하겠습니다.

TCP 를 이용하여 디버깅을 하면 USB 를 연결하지 않아도 adb 명령을 이용하여 app 을 설치하거나, logcat 의 로그를 모으는 등의 USB 와 연결된 것과 동일한 모든 adb 기능을 사용할 수 있습니다.

시중에 이러한 설정을 편리하게 해주는 (원클릭으로 Wireless 디버깅 on/off를 해주는) 유료 앱을 팔고 있던데, 이 App 은 알고보면 아무것도 아닙니다. Rooting 된 폰에서 TCP 디버깅 설정을 on/off 하는 것은 별로 어렵지 않기 때문이죠.

자 이제 adb 에서 제공하는 TCP 를 이용한 디버깅 설정방법을 알아 보겠습니다. 크게 매뉴얼 설정과 자동 설정 두가지로 나누어 살펴보겠습니다.

1. 매뉴얼 설정

먼저 안드로이드 장치의 디버깅 모드를 활성화 한 후 USB 를 연결합니다. TCP 를 이용한 Adb 를 설정하기 위해서는 adb shell 을 이용해야 하기 때문에 처음에 한번은 연결이 필요합니다. 이글 말미에 Rooting 된 폰에서 이를 자동화하는 방법을 소개하겠습니다. (구현은 제공하지 않고 숙제로 남겨두겠습니다.)

TCP 를 이용한 adb 디버깅을 하기 위해서는 현재 무선공유기등과 같은 Wifi 망이 이용가능해야 합니다.

$ adb devices

를 입력하여 현재 연결된 장치를 확인합니다.

그리고는 다음을 입력하여 adb 데몬(device 에서 adb 를 서비스하는 프로세스, adbd)의 설정을 변경합니다.

$ adb tcpip 5555

뒤의 5555 는 5555번 port 를 사용하겠다는 것을 의미합니다. 위와 같이 입력하면 adbd 데몬 프로세스(장치에서 실행되며 실질적인 adb 기능을 수행함)를 tcpip 모드로 재시작합니다. 이제 장치는 tcpip 를 연결할 수 있는 상태가 되었습니다.

(TCP 연결 성공을 체크하기 위해 기존에 연결된 USB 케이블을 해제하시기 바랍니다.)

PC 와 장치를 연결하기 위해서는 장치에 할당된 IP 를 확인해야 합니다. 이는
"설정 > 무선 및 네트워크 > Wi-Fi 설정" 에서 연결된 Wi-Fi 네트워크를 선택하면 확인할 수 있습니다.


저의 경우 192.168.0.4 입니다. 이제 PC 에서 장치로 연결하는 명령을 줍니다.

$ adb connect 192.168.0.4:5555

를 입력하면 연결 성공 메시지를 볼 수 있으며, 이렇게 연결된 이후에는 adb 명령을 동일하게 사용할 수 있습니다.

2. 자동 설정

원터치 Wireless 설정을 지원하는 방법을 살펴보겠습니다. 키포인트는 위에서 실행했던 "adb tcpip <port>" 명령을 어떻게 장치에서 수행하느냐 입니다.

그 명령은 아래와 같습니다.

$ su
# setprop service.adb.tcp.port 5555
# stop adbd
# start adbd

위 명령을 장치에서 실행하기만 하면 되는 것입니다. 위 예제와 같이 su 명령을 이용하여 root 권한을 얻게 되는데, 바로 이런 권한이 필요하기 때문에 production 장치에서는 수행할 수 가 없는 것입니다.

끝으로 안드로이드 App 에서 su 권한을 체크하는 방법을 소개해 드리며, 이 예제 코드와 tcp 설정 스크립트를 이용하면 시중에 돌고 있는 원터치 Wireless Adb 설정 App 을 개발할 수 있습니다. (이 예제도 당연 Rooting 된 폰에서만 root 권한을 얻습니다.) 
☞ su 명령이 수행된다고 가정을 하겠습니다. 만약 su 가 존재하지 않으면 su 는 검색하면 쉽게 구할 수 있습니다. 약간의 보안 방어막을 가한 super user 라는 app 도 있습니다.

Process p;  
try {  
   // Preform su to get root privledges  
   p = Runtime.getRuntime().exec("su");   
  
   // Attempt to write a file to a root-only  
   DataOutputStream os = new DataOutputStream(p.getOutputStream());  
   os.writeBytes("echo \"Do I have root?\" >/system/sd/temporary.txt\n");  
  
   // Close the terminal  
   os.writeBytes("exit\n");  
   os.flush();  
   try {  
      p.waitFor();  
           if (p.exitValue() != 255) {  
              // TODO Code to run on success  
              toastMessage("root");  
           }  
           else {  
               // TODO Code to run on unsuccessful  
               toastMessage("not root");  
           }  
   } catch (InterruptedException e) {  
      // TODO Code to run in interrupted exception  
       toastMessage("not root");  
   }  
} catch (IOException e) {  
   // TODO Code to run in input/output exception  
    toastMessage("not root");  
}  

이것만 가지고 어떻게 만들어요라고 되묻지 마시고, 한번 시도해 보시기 바랍니다. Just do it!


3. 기타

$ adb shell getprop

을 입력하면 현재 장치의 설정 정보를 볼 수 있습니다. 만약 tcpip 로 설정되어 있다면 getprop 에서도 그 목록이 나와야 합니다.

$ adb shell setprop service.adb.tcp.port -1

은 tcp 설정을 해제합니다.

참고:

su 권한을 크랙하는 방법은 아래 글을 참고하세요.


! 추가 확인 필요

아래 명령은 디폴트 설정을 변경한다고 합니다. (위에 설정은 장치가 리부팅되면 usb 모드로 돌아갑니다.) 이게 제대로 동작하는지 확인이 필요합니다. 안된다는 말도 있구요... 확인 요함.

$ adb shell setprop persist.adb.tcp.port 5555

관련글:
2011. 2. 21. 19:00

안드로이드 CPU Usage & Memory Usage 가져오기

안드로이드의 CPU 사용량과 메모리 사용량을 체크하는 방법입니다.

1. CPU 사용량

$ adb shell top -n 1

을 입력하면 CPU 사용량을 출력합니다.

top 에 대한 자세한 설명은 아래 명령을 입력합니다.

$ adb shell top --help

shell 명령을 Android App 에서 수행하려면 다음과 같이 합니다. ( shell 명령 수행시 App 의 권한은 항상 User 이기 때문에 su 권한을 필요로 하는 명령은 수행할 수 없음에 유의하셔야 합니다. )


                Runtime runtime = Runtime.getRuntime(); 
                Process process; 
                String res = "-0-"; 
                try { 
                        String cmd = "top -n 1"; 
                        process = runtime.exec(cmd); 
                        InputStream is = process.getInputStream(); 
                        InputStreamReader isr = new InputStreamReader(is); 
                        BufferedReader br = new BufferedReader(isr); 
                        String line ; 
                        while ((line = br.readLine()) != null) { 
                                String segs[] = line.trim().split("[ ]+"); 
                                if (segs[0].equalsIgnoreCase([Your Process ID])) { 
                                        res = segs[1]; 
                                        break; 
                                } 
                        } 
                } catch (Exception e) { 
                        e.fillInStackTrace(); 
                        Log.e("Process Manager", "Unable to execute top command"); 
                } 

2. 메모리 사용량

$ adb shell procrank

procrank 명령이 가장 정확한 메모리 사용 정보를 보여준다고 합니다. (by Dianne Hackborn)

특히, 
uss 칼럼은 app 고유의 메모리 사용량을 나타내고,
pss 는 또한 프로세스간에 공유되는 메모리 량을 포함하고 있습니다.

하지만 이 명령은 production device 에는 지원하지 않을수 있습니다. 그때는 다음의 명령을 사용합니다.

$ adb shell dumpsys meminfo
$ adb shell dumpsys meminfo [processname]

위의 명령을 들을 이용하여 CPU 와 Memory 의 사용량을 차트 등으로 한눈에 보여줄 수 있습니다.

참고:

아래 링크 추가


2011. 2. 14. 23:38

Droid Explorer

A tool to manage your rooted android device with the simplicity of Windows Explorer.


코드에 눈여겨 볼점은 Java 가 아닌 C# 으로 작성되었다는 것입니다. 개발환경은 Visual Studio 2010 이므로 해당 IDE 가 설치되어 있어야 빌드가 가능합니다. 용량이 커서 일부만 첨부하며 전체 소스는 원본 링크를 참고하시기 바랍니다.


2011. 2. 10. 07:13

Security Permissions in Android

As we all know whenever we use a particular feature or API we need to request the permission in AndroidManifest.xml file with uses-permission element. If we don’t specify any permissions, then the application will not have any permission and application can do anything that does not require a permission.This link explains the permissions in android in more detail and this link lists the permissions in Android.

Permissions are granted to the application by package installer while installing. But not all the permissions will be granted to the system. There are some system permission which will not be granted to the user applications, but only to the system applications. Following are some of the permissions that may NOT be granted to the user application.

android.permission.ACCESS_CHECKIN_PROPERTIES
android.permission.ACCESS_SURFACE_FLINGER
android.permission.ACCOUNT_MANAGER
android.permission.BIND_APPWIDGET
android.permission.BIND_DEVICE_ADMIN
android.permission.BIND_INPUT_METHOD
android.permission.BIND_WALLPAPER
android.permission.BRICK
android.permission.BROADCAST_PACKAGE_REMOVED
android.permission.BROADCAST_SMS
android.permission.BROADCAST_WAP_PUSH
android.permission.CALL_PRIVILEGED
android.permission.CHANGE_COMPONENT_ENABLED_STATE
android.permission.CLEAR_APP_USER_DATA
android.permission.CONTROL_LOCATION_UPDATES
android.permission.DELETE_CACHE_FILES
android.permission.DELETE_PACKAGES
android.permission.DEVICE_POWER
android.permission.DIAGNOSTIC
android.permission.FACTORY_TEST
android.permission.FORCE_BACK
android.permission.GLOBAL_SEARCH
android.permission.HARDWARE_TEST
android.permission.INJECT_EVENTS
android.permission.INSTALL_LOCATION_PROVIDER
android.permission.INSTALL_PACKAGES
android.permission.INTERNAL_SYSTEM_WINDOW
android.permission.MANAGE_APP_TOKENS
android.permission.MASTER_CLEAR
android.permission.READ_FRAME_BUFFER
android.permission.READ_INPUT_STATE
android.permission.REBOOT
android.permission.SET_ACTIVITY_WATCHER
android.permission.SET_ORIENTATION
android.permission.SET_PREFERRED_APPLICATIONS
android.permission.SET_TIME
android.permission.STATUS_BAR
android.permission.UPDATE_DEVICE_STATS
android.permission.WRITE_GSERVICES
android.permission.WRITE_SECURE_SETTINGS

To get these permissions, the application must be signed with the key which used to sign the platform. This may be different for manufacturers. So it practically not possible to get these permissions granted to a user application.

Note: While playing with PowerManager.reboot I was so stupid I thought my application will be granted the permission android.permission.REBOOT, but it was not granted. Then I created an application requesting all the permissions and above list of permissions are not granted. Hope this will help you when you request a permission next time.

Source: http://www.krvarma.com/posts/android/security-permissions-in-android/

2011. 1. 27. 01:32

Gingerbread NDK Awesomeness

원문: http://android-developers.blogspot.com/2011/01/gingerbread-ndk-awesomeness.html

내용중에서,


 In fact, with these new tools, applications targeted at Gingerbread or later can be implemented entirely in C++; you can now build an entire Android application without writing a single line of Java.
 사실, 이 새로운 도구를 통해 진저브레드 혹은 그 이후 버전을 지원하는 어플리케이션을 순수하게 C++로만 구현할 수 있게 되었습니다.   한 줄의 Java 코드 없이 전체 안드로이드 어플리케이션을 만들 수 있게 된 것입니다.


를 보면 C++ 만으로 안드로이드 App 구현이 가능하다는거네요. 원문 작성 날짜가 Posted by Tim Bray on 11 January 2011 at 1:13 PM 이니 최근의 일이군요.
2010. 12. 8. 03:28

실행가능한 애플리케이션 목록 가져오기

안드로이드에 설치된 애플리케이션 중에서, 실행가능한 애플리케이션의 목록을 가져오는 방법은 API Demos 의 Grid1 클래스 예제에 나와 있습니다. 


public class Grid1 extends Activity {

    GridView mGrid;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        loadApps(); // do this in onresume?

        setContentView(R.layout.grid_1);
        mGrid = (GridView) findViewById(R.id.myGrid);
        mGrid.setAdapter(new AppsAdapter());
    }

    private List<resolveinfo> mApps;

    private void loadApps() {
        Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

        mApps = getPackageManager().queryIntentActivities(mainIntent, 0);
    }

    public class AppsAdapter extends BaseAdapter {
        public AppsAdapter() {
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView i;

            if (convertView == null) {
                i = new ImageView(Grid1.this);
                i.setScaleType(ImageView.ScaleType.FIT_CENTER);
                i.setLayoutParams(new GridView.LayoutParams(50, 50));
            } else {
                i = (ImageView) convertView;
            }

            ResolveInfo info = mApps.get(position);
            i.setImageDrawable(info.activityInfo.loadIcon(getPackageManager()));

            return i;
        }


        public final int getCount() {
            return mApps.size();
        }

        public final Object getItem(int position) {
            return mApps.get(position);
        }

        public final long getItemId(int position) {
            return position;
        }
    }

}

원래는 실행가능한 애플리케이션의 목록을 Adb 또는 shell 명령을 이용하여 가져올 수 있어야 했는데, 여의치 않아 실행 목록 정보를 제공하는 Agent 를 자동으로 심는 방법을 선택하였습니다.
2010. 10. 9. 20:51

[펌] 소스 #3 - Dialog 없이 wheel 만 있는 ProgressBar 만들고 WebView 위에서 사용하기

혹 나중에 필요할 것 같아서,,, androidpub.com 링크


2010. 8. 16. 19:39

Local Service and Remote Service ( AIDL Service )

안드로이드에는 두종류의 서비스가 있습니다.

* 로컬 서비스 (local service)

애플리케이션내에만 서비스 하는 것으로 다른 애플리케이션에서는 접근할 수 없습니다. 로컬서비스는 백그라운드 작업을 쉽게 구현하도록 하기 위해서입니다. 

* 원격 서비스 (remote service)

원격서비스는 애플리케이션간에 Interprocess Communication 을 지원하기 위한 것입니다. 이를 지원하기 위해 AIDL (Android Interface Definition Lanaguage)를 사용하기 때문에 AIDL Service 라고도 부릅니다. 

에제를 통해서 살펴보는게 나을 것입니다.

로컬 서비스 예제

아래 첨부는 이클립스 프로젝트 입니다.


BackgroundService.java

package com.sskk.sample.localservicetest.service;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import com.sskk.sample.localservicetest.MainActivity;
import com.sskk.sample.localservicetest.R;

public class BackgroundService extends Service
{
	private NotificationManager notificationMgr;
	@Override
	public void onCreate() {
		super.onCreate();
		notificationMgr =(NotificationManager)getSystemService(
				NOTIFICATION_SERVICE);
		displayNotificationMessage("starting Background Service");
		Thread thr = new Thread(null, new ServiceWorker(), "BackgroundService");
		thr.start();
	}
	class ServiceWorker implements Runnable
	{
		public void run() {
			// do background processing here...
			// stop the service when done...
			// BackgroundService.this.stopSelf();
		}
	}
	@Override
	public void onDestroy()
	{
		displayNotificationMessage("stopping Background Service");
		super.onDestroy();
	}
	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}
	private void displayNotificationMessage(String message)
	{
		Notification notification = new Notification(R.drawable.note,
				message,System.currentTimeMillis());
		PendingIntent contentIntent =
			PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
		notification.setLatestEventInfo(this, "Background Service",message,
				contentIntent);
		notificationMgr.notify(R.id.app_notification_id, notification);
	}
}


원격 서비스 예제



1. 간단한 AIDL 구현 예제

IStockQuoteService.aidl

package com.sskk.sample.service;

interface IStockQuoteService {
	double getQuote(String ticker);
}

StockQuoteService2.java

package com.sskk.sample.service;

import com.sskk.sample.service.IStockQuoteService;
import com.sskk.sample.service.IStockQuoteService.Stub;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class StockQuoteService extends Service {
	private static final String TAG = "StockQuoteService";
	
	public class StockQuoteServiceImple extends IStockQuoteService.Stub {

		@Override
        public double getQuote(String ticker) throws RemoteException {
	        Log.v(TAG, "종목 " + ticker + "에 대해 getQuote() 호출됨");
	        return 20.0;
        }
		
	}

	@Override
    public IBinder onBind(Intent p_intent) {
	    Log.v(TAG, "onBind 호출됨");
	    return new StockQuoteServiceImple();
    }

	@Override
    public void onCreate() {
	    super.onCreate();
	    
	    Log.v(TAG, "onCreate 호출됨");
    }

	@Override
    public void onDestroy() {
	    super.onDestroy();
	    
	    Log.v(TAG, "onDestroy 호출됨");
    }

	@Override
    public void onStart(Intent p_intent, int p_startId) {
	    super.onStart(p_intent, p_startId);
	    
	    Log.v(TAG, "onStart 호출됨");
    }
	
	
}

2. 객체를 전달하는 AIDL 예제

Person.aidl

package com.sskk.sample.service;
parcelable Person;
Person.java

package com.sskk.sample.service;

import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {
	private int mAge;
	private String mName;
	public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {

		@Override
        public Person createFromParcel(Parcel in) {	        
	        return new Person(in);
        }

		@Override
        public Person[] newArray(int size) {
	        return new Person[size];
        }
		
	};
	
	public Person() {
		
	}
	
	private Person(Parcel in) {
		readFromParcel(in);
	}
	
	public void readFromParcel(Parcel in) {
		mAge = in.readInt();
		mName = in.readString();
	}

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel out, int flags) {
		out.writeInt(mAge);
		out.writeString(mName);
	}
	
	public int getAge() {
		return mAge;
	}
	
	public void setAge(int age) {
		mAge = age;
	}
	
	public String getName() {
		return mName;
	}
	
	public void setName(String name) {
		mName = name;
	}

}

StockQuoteService2.aidl

package com.sskk.sample.service;
import com.sskk.sample.service.Person;


interface IStockQuoteService2 {
	String getQuote(in String ticker, in Person requester);
}
StockQuoteService2.java

package com.sskk.sample.service;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.sskk.sample.aidltest.NotiActivity;
import com.sskk.sample.aidltest.R;

public class StockQuoteService2 extends Service {
	private NotificationManager mNotificationManager;
	
	public class StockQuoteServiceImple2 extends IStockQuoteService2.Stub {

		@Override
        public String getQuote(String ticker, Person requester) throws RemoteException {
	        // TODO Auto-generated method stub
	        return "안녕하세요, " + requester.getName() + "!" + ticker + "의 주가는 20.0 입니다.";
        }				
	}

	@Override
	public void onCreate() {
	    super.onCreate();
	    
	    mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
	    displayNotificationMessage("onCreate 가 호출됨");	    	    
	}
	
	@Override
	public void onDestroy() {
		displayNotificationMessage("onDestroy 가 호출됨");
	    super.onDestroy();
	}
	
	@Override
	public void onStart(Intent intent, int startId) {
	    super.onStart(intent, startId);
	}
	
	@Override
	public IBinder onBind(Intent intent) {
	    displayNotificationMessage("onBind 가 호출됨");
	    return new StockQuoteServiceImple2();
	}		
	
	private void displayNotificationMessage(String message) {
		Notification notification = new Notification(R.drawable.note, message, System.currentTimeMillis());
		
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, NotiActivity.class), 0);
		notification.setLatestEventInfo(this, "StockQuoteService2", message, contentIntent);
		
		mNotificationManager.notify(R.id.app_notification_id, notification);
	}
	
}

3. AndroidMnifiest.xml 에 서비스 등록하기


	
		
			
				
			
		
		
			
				
			
		
	
 
참고: Pro Android 2
2010. 8. 15. 05:54

HttpClient 및 URL 테스트

회차별 로또 당첨번호를 웹에서 가져오는 코드를 구현함에 있어 HttpClient 와 URL 을 이용하는 두가지 방법에 대해서 모두 작성해 보았습니다. 당첨번호를 가져오는 방법은 html 응답을 직접 파싱하여 원하는 당첨번호만 추출하는 방법인데 이는 웹페이지가 변경되면 올바르게 동작하지 않는 문제가 있습니다. 그치만 외부에 공개되지 않은 정보를 웹에서 긁어 올 수 있는 아주 쉬운 방법입니다.

1. HttpClient 를 이용하는 방법

	HttpClient client = new DefaultHttpClient();
	HttpGet request = new HttpGet();
			
	request.setURI(new URI("http://www.645lotto.net/Confirm/number.asp?sltSeq=" + game));
			
	HttpResponse response = client.execute(request);
			
	BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
2. URL 을 이용하는 방법

	URL url = new URL("http://www.645lotto.net/Confirm/number.asp?sltSeq=" + game);
	BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
3. HttpClientTest.java
아래는 작성한 풀 소스입니다. 이 소스를 테스트 하기 위해서는 android.permission.INTERNET 권한이 필요합니다.

package com.sskk.example.HttpClientTest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

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

public class HttpClientTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ArrayList numbers = GetLottoNumber1(1);
        for(Integer number : numbers) {
        	System.out.print(number + " ");
        }
        System.out.println();
        
        numbers = GetLottoNumber2(2);
        for(Integer number : numbers) {
        	System.out.print(number + " ");
        }
        System.out.println();
    }
    
    public static ArrayList GetLottoNumber1(final int game) {
		ArrayList numberList = new ArrayList();	
		
		try {
			HttpClient client = new DefaultHttpClient();
			HttpGet request = new HttpGet();
			
			request.setURI(new URI("http://www.645lotto.net/Confirm/number.asp?sltSeq=" + game));
			
			HttpResponse response = client.execute(request);
			
			BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
			String tempStr;
			StringBuilder htmlSource = new StringBuilder();
			
			while((tempStr = in.readLine()) != null) {
				htmlSource.append(tempStr);
			}
			in.close();
			
			int first = htmlSource.indexOf("class=\"sboll2");
			if(first == -1) return null;
			int last = htmlSource.indexOf("/div>", first);
			if(last == -1) return null;
			
			String numberHtml = htmlSource.substring(first, last);
			
			final String search = "../images/Comm/s";
			final int length = search.length();			
			
			int firstIndex = 0;			
			while(true) {
				int found = numberHtml.indexOf(search, firstIndex);
				if(found == -1) break;
				
				int dotFound = numberHtml.indexOf(".", found + length);
				if(dotFound == -1) break;
				
				String numberStr = numberHtml.substring(found + length, dotFound);
				
				try {
					Integer number = Integer.parseInt(numberStr);
					numberList.add(number);
					// System.out.println(number);
				} catch(Exception e) { }				
				
				firstIndex = found + length;
			}				
						
		}
		catch(Exception e) { 
			e.printStackTrace();
		}
		
		if(numberList.size() != 7) {
			return null;
		}		
		return numberList;
	}
    
    public static ArrayList GetLottoNumber2(final int game) {
		ArrayList numberList = new ArrayList();	
		
		try {
			URL url = new URL("http://www.645lotto.net/Confirm/number.asp?sltSeq=" + game);
			BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
			String tempStr;
			StringBuilder htmlSource = new StringBuilder();
			
			while((tempStr = in.readLine()) != null) {
				htmlSource.append(tempStr);
			}
			in.close();
			
			int first = htmlSource.indexOf("class=\"sboll2");
			if(first == -1) return null;
			int last = htmlSource.indexOf("/div>", first);
			if(last == -1) return null;
			
			String numberHtml = htmlSource.substring(first, last);
			
			final String search = "../images/Comm/s";
			final int length = search.length();			
			
			int firstIndex = 0;			
			while(true) {
				int found = numberHtml.indexOf(search, firstIndex);
				if(found == -1) break;
				
				int dotFound = numberHtml.indexOf(".", found + length);
				if(dotFound == -1) break;
				
				String numberStr = numberHtml.substring(found + length, dotFound);
				
				try {
					Integer number = Integer.parseInt(numberStr);
					numberList.add(number);
					// System.out.println(number);
				} catch(Exception e) { }				
				
				firstIndex = found + length;
			}				
						
		}catch(MalformedURLException e) { }
		catch(IOException e) { 
			e.printStackTrace();
		}
		
		if(numberList.size() != 7) {
			return null;
		}		
		return numberList;
	}
}

2010. 8. 15. 04:48

ContentProvider Example

Pro Android 2 책에 나오는 BookProvider 테스트 샘플을 이클립스로 만들어 보았습니다. 

MainActivity 는 BookProvider 를 간단하게 테스트하는 것으로, 중요한 건 Provider 를 만들고 등록하는 방법과 그를 어떻게 이용하는 것입니다. 첨부는 이클립스 프로젝트.


BookProvider.java

package com.sskk.example.bookprovidertest.provider;

import java.util.HashMap;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

import com.sskk.example.bookprovidertest.provider.BookProviderMetaData.BookTableMetaData;

public class BookProvider extends ContentProvider
{
	private final static String TAG="tag";

	//Create a Projection Map for Columns
	//Projection maps are similar to "as" construct in an sql
	//statement whereby you can rename the
	//columns.
	private static HashMap sBooksProjectionMap;
	static
	{
		sBooksProjectionMap = new HashMap();
		sBooksProjectionMap.put(BookTableMetaData._ID, BookTableMetaData._ID);
		//name, isbn, author
		sBooksProjectionMap.put(BookTableMetaData.BOOK_NAME
				, BookTableMetaData.BOOK_NAME);
		sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN
				, BookTableMetaData.BOOK_ISBN);
		sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR
				, BookTableMetaData.BOOK_AUTHOR);
		//created date, modified date
		sBooksProjectionMap.put(BookTableMetaData.CREATED_DATE
				, BookTableMetaData.CREATED_DATE);
		sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE
				, BookTableMetaData.MODIFIED_DATE);
	}
	//Provide a mechanism to identify all the incoming uri patterns.
	private static final UriMatcher sUriMatcher;
	private static final int INCOMING_BOOK_COLLECTION_URI_INDICATOR = 1;

	private static final int INCOMING_SINGLE_BOOK_URI_INDICATOR = 2;
	static {
		sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		sUriMatcher.addURI(BookProviderMetaData.AUTHORITY
				, "books"
				, INCOMING_BOOK_COLLECTION_URI_INDICATOR);
		sUriMatcher.addURI(BookProviderMetaData.AUTHORITY
				, "books/#",
				INCOMING_SINGLE_BOOK_URI_INDICATOR);
	}
	// Deal with OnCreate call back
	private DatabaseHelper mOpenHelper;

	@Override
	public boolean onCreate() {
		mOpenHelper = new DatabaseHelper(getContext());
		return true;
	}

	private static class DatabaseHelper extends SQLiteOpenHelper 
	{
		DatabaseHelper(Context context) 
		{
			super(context, BookProviderMetaData.DATABASE_NAME, null
					, BookProviderMetaData.DATABASE_VERSION);
		}

		//Create the database
		@Override
		public void onCreate(SQLiteDatabase db) 
		{
			db.execSQL("CREATE TABLE " + BookTableMetaData.TABLE_NAME + " ("
					+ BookProviderMetaData.BookTableMetaData._ID
					+ " INTEGER PRIMARY KEY,"
					+ BookTableMetaData.BOOK_NAME + " TEXT,"
					+ BookTableMetaData.BOOK_ISBN + " TEXT,"
					+ BookTableMetaData.BOOK_AUTHOR + " TEXT,"
					+ BookTableMetaData.CREATED_DATE + " INTEGER,"
					+ BookTableMetaData.MODIFIED_DATE + " INTEGER"
					+ ");");
		}
		//Deal with version changes
		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 
		{
			Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
					+ newVersion + ", which will destroy all old data");
			db.execSQL("DROP TABLE IF EXISTS " + BookTableMetaData.TABLE_NAME);
			onCreate(db);
		}
	}

	@Override
	public String getType(Uri uri) 
	{
		switch (sUriMatcher.match(uri)) 
		{
			case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
				return BookTableMetaData.CONTENT_TYPE;

			case INCOMING_SINGLE_BOOK_URI_INDICATOR:
				return BookTableMetaData.CONTENT_ITEM_TYPE;

			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection
			, String[] selectionArgs, String sortOrder)
	{
		SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
		switch (sUriMatcher.match(uri))
		{
			case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
				qb.setTables(BookTableMetaData.TABLE_NAME);
				qb.setProjectionMap(sBooksProjectionMap);
				break;

			case INCOMING_SINGLE_BOOK_URI_INDICATOR:
				qb.setTables(BookTableMetaData.TABLE_NAME);
				qb.setProjectionMap(sBooksProjectionMap);
				qb.appendWhere(BookTableMetaData._ID + "="
						+ uri.getPathSegments().get(1));
				break;

			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}

		// If no sort order is specified use the default
		String orderBy;
		if (TextUtils.isEmpty(sortOrder)) {
			orderBy = BookTableMetaData.DEFAULT_SORT_ORDER;
		} else {
			orderBy = sortOrder;
		}

		// Get the database and run the query
		SQLiteDatabase db =
			mOpenHelper.getReadableDatabase();

		Cursor c = qb.query(db, projection, selection,
				selectionArgs, null, null, orderBy);

		int i = c.getCount();
		// Tell the cursor what uri to watch,
		// so it knows when its source data changes
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) 
	{
		// Validate the requested uri
		if (sUriMatcher.match(uri) != INCOMING_BOOK_COLLECTION_URI_INDICATOR) {
			throw new IllegalArgumentException("Unknown URI " + uri);
		}
		Long now = Long.valueOf(System.currentTimeMillis());

		//validate input fields
		// Make sure that the fields are all set
		if (values.containsKey(BookTableMetaData.CREATED_DATE) == false) {
			values.put(BookTableMetaData.CREATED_DATE, now);
		}

		if (values.containsKey(BookTableMetaData.MODIFIED_DATE) == false) {
			values.put(BookTableMetaData.MODIFIED_DATE, now);
		}
		if (values.containsKey(BookTableMetaData.BOOK_NAME) == false) {
			throw new SQLException(
					"Failed to insert row because Book Name is needed " + uri);
		}

		if (values.containsKey(BookTableMetaData.BOOK_ISBN) == false) {
			values.put(BookTableMetaData.BOOK_ISBN, "Unknown ISBN");
		}
		if (values.containsKey(BookTableMetaData.BOOK_AUTHOR) == false) {
			values.put(BookTableMetaData.BOOK_ISBN, "Unknown Author");
		}

		SQLiteDatabase db = mOpenHelper.getWritableDatabase();
		long rowId = db.insert(BookTableMetaData.TABLE_NAME
				, BookTableMetaData.BOOK_NAME, values);

		if (rowId > 0) {
			Uri insertedBookUri = ContentUris.withAppendedId(
					BookTableMetaData.CONTENT_URI, rowId);
			getContext().getContentResolver().notifyChange(insertedBookUri, null);
			return insertedBookUri;
		}
		throw new SQLException("Failed to insert row into " + uri);
	}

	@Override
	public int update(Uri uri, ContentValues values, String where, String[] whereArgs)
	{
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();
		int count;
		switch (sUriMatcher.match(uri)) 
		{
			case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
				count = db.update(BookTableMetaData.TABLE_NAME,
						values, where, whereArgs);
				break;
			case INCOMING_SINGLE_BOOK_URI_INDICATOR:
				String rowId = uri.getPathSegments().get(1);
				count = db.update(BookTableMetaData.TABLE_NAME
						, values
						, BookTableMetaData._ID + "=" + rowId
						+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : "")
						, whereArgs);
				break;
			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}
		getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}

	@Override
	public int delete(Uri uri, String where, String[] whereArgs) 
	{
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();
		int count;
		switch (sUriMatcher.match(uri)) 
		{
			case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
				count = db.delete(BookTableMetaData.TABLE_NAME, where, whereArgs);
				break;
			case INCOMING_SINGLE_BOOK_URI_INDICATOR:
				String rowId = uri.getPathSegments().get(1);
				count = db.delete(BookTableMetaData.TABLE_NAME
						, BookTableMetaData._ID + "=" + rowId
						+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : "")
						, whereArgs);
				break;
			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}
		getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}
}

BookProviderMetaData.java

package com.sskk.example.bookprovidertest.provider;

import android.net.Uri;
import android.provider.BaseColumns;

public class BookProviderMetaData
{
	public static final String AUTHORITY = "com.sskk.example.bookprovidertest.provider.BookProvider";
	public static final String DATABASE_NAME = "book.db";
	public static final int DATABASE_VERSION = 1;
	public static final String BOOKS_TABLE_NAME = "books";

	private BookProviderMetaData() {}

	//inner class describing BookTable
	public static final class BookTableMetaData implements BaseColumns
	{
		private BookTableMetaData() {}
		public static final String TABLE_NAME = "books";

		//uri and MIME type definitions
		public static final Uri CONTENT_URI =
			Uri.parse("content://" + AUTHORITY + "/books");

		public static final String CONTENT_TYPE =
			"vnd.android.cursor.dir/vnd.androidbook.book";

		public static final String CONTENT_ITEM_TYPE =
			"vnd.android.cursor.item/vnd.androidbook.book";

		public static final String DEFAULT_SORT_ORDER = "modified DESC";

		//Additional Columns start here.
		//string type
		public static final String BOOK_NAME = "name";

		//string type
		public static final String BOOK_ISBN = "isbn";

		//string type
		public static final String BOOK_AUTHOR = "author";

		//Integer from System.currentTimeMillis()
		public static final String CREATED_DATE = "created";

		//Integer from System.currentTimeMillis()
		public static final String MODIFIED_DATE = "modified";
	}
}

MainActivity.java

package com.sskk.example.bookprovidertest;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import com.sskk.example.bookprovidertest.provider.BookProviderMetaData;

public class MainActivity extends Activity {
	private static final String TAG = "MainActivity"; 	

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		deleteTest();

		insertTest();

		readTest();
	}

	private void readTest() {
		ContentResolver contentResolver = getContentResolver(); 
		Cursor cursor = contentResolver.query(BookProviderMetaData.BookTableMetaData.CONTENT_URI, null, null, null, null);
		
		// 행 갯수 출력
		Log.d(TAG, "Cursor Count: " + cursor.getCount());

		// 커서 내용 읽기
		if(cursor.moveToFirst() == true) {        	        	
			do {
				int nameColumn = cursor.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_NAME);
				int authorColumn = cursor.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR);
				int isbnColumn = cursor.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_ISBN);

				String name = cursor.getString(nameColumn);
				String author = cursor.getString(authorColumn);
				String isbn = cursor.getString(isbnColumn);

				Log.d(TAG, name + " " + author + " " + isbn);
			} while(cursor.moveToNext());
		}
	}

	private void deleteTest() {
		ContentResolver contentResolver = getContentResolver(); 		
		
		contentResolver.delete(BookProviderMetaData.BookTableMetaData.CONTENT_URI, null, null);
		
		Cursor cursor = contentResolver.query(BookProviderMetaData.BookTableMetaData.CONTENT_URI, null, null, null, null);
		// 행 갯수 출력
		Log.d(TAG, "Cusor Count: " + cursor.getCount());
	}

	private void insertTest() {
		ContentResolver contentResolver = getContentResolver(); 
		Cursor cursor = contentResolver.query(BookProviderMetaData.BookTableMetaData.CONTENT_URI, null, null, null, null);

		if(cursor.getCount() != 0) return;
		// 행 추가
		ContentValues values = new ContentValues();
		values.put(BookProviderMetaData.BookTableMetaData.BOOK_NAME, "Sonagi");
		values.put(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR, "HwangSunWon");
		values.put(BookProviderMetaData.BookTableMetaData.BOOK_ISBN, "isbn-0033");

		Uri uri = contentResolver.insert(BookProviderMetaData.BookTableMetaData.CONTENT_URI, values);

		values = new ContentValues();
		values.put(BookProviderMetaData.BookTableMetaData.BOOK_NAME, "HaakHaak");
		values.put(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR, "LeeOiSu");
		values.put(BookProviderMetaData.BookTableMetaData.BOOK_ISBN, "isbn-0034");
		uri = contentResolver.insert(BookProviderMetaData.BookTableMetaData.CONTENT_URI, values);
	}
}
참고: 안드로이드2 마스터북 Pro Android 2 교재