2013년 10월 15일 화요일

문자 애플리케이션 킷캣 대응하기

스캇 메인(Scott Main)과 데이비드 브라운(David Braun) 작성

문자 메시지를 주고받는 것은 휴대 장비의 근본적인 기능입니다. 많은 개발자들은 안드로이드의  문자 경험을 성공적으로 향상시킨 앱들을 개발해왔습니다. 일부 사람들은 감추어진 API들을 사용하여 문자 앱을 개발하였습니다. 우리는 이런 사용을 비추하는데 숨겨진 API들은 삭제될 수도 있고 새로운 단말기에서 호환성이 검증되지 않기 때문입니다. 그래서 문자 앱을 만들때 전체적인 지원 API 집합을 제공하고 문자 경험을 보다 예측가능하게 만들기 위해, 안드로이드 4.4(킷캣, KitKat)은 기존 API들을 공개하고 기본 문자 애플리케이션 개념을 추가하였습니다. 사용자는 시스템 설정에서 기본 문자 애플리케이션을 선택할 수 있죠.

이것은 이전 플랫폼 버전에서 숨겨진 문자 API를 사용했다면 조금의 수정하면 올해 출시 될 안드로이드 4.4에서도 제대로 동작하는 것을 의미합니다.

여러분의 앱을 기본 문자 앱으로 하기

안드로이드 4.4에서 단지 하나의 애플리케이션만 새로 도입된 SMS_DELIVER_ACTION 인텐트를 받을 수 있습니다. 새 문자 메시지가 오면 시스템은 이 인텐트를 브로드캐스트합니다. 어떤 앱이 이 브로드캐스트를 받을지는 시스템 설정에서 사용자가 기본 SMS 앱을 골라 결정합니다. 마찬가지로 기본 문자 앱만이 MMS가 도착했을 때 새 인텐트 WAP_PUSH_DELIVER_ACTION를 받습니다.

새 메시지를 받길 원하는 다른 앱들은 SMS가 왔을 때 대신에 SMS_RECEIVED_ACTION를 받을 수 있습니다. 하지만 (사용자가 기본 SMS 앱으로 지정한) SMS_DELIVER_ACTION 브로드캐스트를 받는 앱만 android.provider.Telephony 클래스와 상속된 클래스로 정의된 문자 프로바이더를 작성할 수 있습니다. 이것은 기존의 앱이 안드로이드 4.4에서 기본 문자 앱이 사용가능해질 때 앱이 망가지지 않고 SMS 프로바이더가 조용하게 실패하고, 문자 앱을 즉시 업데이트하지 않아도 된다는 점에서 중요합니다.

  • 브로드캐스트 리시버에서 SMS_DELIVER_ACTION ("android.provider.Telephony.SMS_DELIVER")에 대한 인텐트 필터를 포함합니다. 브로드캐스트 리서버는 BROADCAST_SMS 권한을 반드시 가져야 합니다.

    이 권한이 앱이 수신된 문자 메시지를 직접 받게 합니다.
  • 브로드캐스트 리시버에서 WAP_PUSH_DELIVER_ACTION("android.provider.Telephony.WAP_PUSH_DELIVER") 인텐트 필터를 MIME 타입 "application/vnd.wap.mms-message"와 함께 포함합니다. 이 브로드캐스트 리시버는 BROADCAST_WAP_PUSH 권한을 반드시 요구해야 합니다.

    이 권한이 앱이 수신된 MMS 메시지를 직접 받게 합니다.
  • 새 메시지를 전달할 액티비티에서 ACTION_SENDTO("android.intent.action.SENDTO")를 위한 인텐트 필터를 sms:, smsto:, mms:, mmsto: 스키마와 같이 포함합니다.

    이 필터가 메시지를 전달하길 원하는 다른 앱으로 부터 인텐트를 받게 합니다.
  • 서비스에서 sms:, smsto:, mms:, mmsto: 스키마와 함께 ACTION_RESPONSE_VIA_MESSAGE("android.intent.action.RESPOND_VIA_MESSAGE") 인텐트 필터를 포함합니다. 서비스는 SEND_RESPOND_VIA_MESSAGE 권한을 필요합니다.

    이 필터는 사용자가 여러분의 앱을 사용하여 즉시 문자 메시지를 받으며 걸려온 전화를 응답하는 것을 가능하게 합니다.
필요한 콤포넌트와 인텐트 필터의 예입니다.

<manifest>
    ...
    <application>
        <!-- BroadcastReceiver that listens for incoming SMS messages -->
        <receiver android:name=".SmsReceiver"
                android:permission="android.permission.BROADCAST_SMS">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_DELIVER" />
            </intent-filter>
        </receiver>

        <!-- BroadcastReceiver that listens for incoming MMS messages -->
        <receiver android:name=".MmsReceiver"
            android:permission="android.permission.BROADCAST_WAP_PUSH">
            <intent-filter>
                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
                <data android:mimeType="application/vnd.wap.mms-message" />
            </intent-filter>
        </receiver>

        <!-- Activity that allows the user to send new SMS/MMS messages -->
        <activity android:name=".ComposeSmsActivity" >
            <intent-filter>
                <action android:name="android.intent.action.SEND" />                
                <action android:name="android.intent.action.SENDTO" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="sms" />
                <data android:scheme="smsto" />
                <data android:scheme="mms" />
                <data android:scheme="mmsto" />
            </intent-filter>
        </activity>

        <!-- Service that delivers messages from the phone "quick response" -->
        <service android:name=".HeadlessSmsSendService"
                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
                 android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="sms" />
                <data android:scheme="smsto" />
                <data android:scheme="mms" />
                <data android:scheme="mmsto" />
            </intent-filter>
        </service>
    </application>
</manifest>
SMS_RECEIVED_ACTION 브로드캐스트의 어떤 필터를 사용하는 기존 앱들은 안드로이드 4.4에서도 동작합니다. 하지만 새 메시지의 관찰자로서만 동작합니다. SMS_DELIVER_ACTION 브로드캐스트를 받지 않는 한 안드로이드 4.4의 문자 프로바이더를 쓸 수 없습니다.

안드로이드 4.4에서 시작되면 실시간에 플랫폼 버전을 확인하여  PackageManager.setComponentEnabledSetting()를 사용하여 SMS_RECEIVED_ACTION 브로드캐스트 수신을 멈추어야 합니다. 하지만 여러 분의 앱이 휴대전화 번호 인증과 같은 특별한 문자 메시지만 읽기를 원한다면 여전히 저 브로드캐스트를 받을 수 있습니다. SMS_RECEIVED_ACTION 인텐트를 안드로이드 4.4에서 다룬다면 다른 앱도 브로드캐스트를 필요할지 모르니 취소하지 말아야 합니다.

: 두 문자 브로드캐스트를 구별하기 위해 SMS_RECEIVED_ACTION를 단순히 "시스템이 문자를 받는다"로 생각하세요. SMS_DELIVER_ACTION는 기본 문자앱이기 때문에 "시스템이 여러분의 앱에 문자를 전달해준다"로 생각하고요.

기본 문자 앱이 아닌 경우에 비활성화되는 기능들

애플리케이션이 현재 기본 문자 앱으로 선택되지 않았다면 문자 프로바이더 작성 기능없이는 보내는 어떤 메시지도 사용자의 기본 문자 앱으로 나타나지 않기 때문에 새 메시지 전송하기 기능을 비활성화해야하는 것이 중요합니다.그래서 액티비티가 재게될 때 현재 기본 문자 앱의 패키지 네임을 반환하는 Telephony.Sms.getDefaultSmsPackage()를 질의하여 여러분의 앱이 기본 문자 앱인지 확인해야 합니다. 여러분의 패키지 이름과 일치하지 않는다면 메시지 전송 기능을 비활성화 합니다.
메시지 전송과 수신을 활성화하기 위해 시스템이 제공하는 다이얼로그를 띄울 수 있습니다. 이 다이얼로그에서 사용자는 여러분의 앱을 기본 앱으로 설정할 수 있습니다. 다이얼로그를 표시하기 위해 Sms.Intents.EXTRA_PACKAGE_NAME를 키로 삼고 여러분의 패키지 네임을 문자 값으로 삼는 extra를 포함한 Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT 인텐트와 함께 startActivity()를 호출합니다.

우아한 사용자 경험을 제공하기 위해 액티비티가 재개될 때 앱이 기본 문자 앱인지 확인하고, 사용자가 기본 문자 앱을 변경하게 하는 메시지를 담게 사용자 인터페이스를 수정하세요. 예를 들어 액티비티 코드는 이렇게 될 수 있습니다.
public class ComposeSmsActivity extends Activity {

    @Override
    protected void onResume() {
        super.onResume();

        final String myPackageName = getPackageName();
        if (!Telephony.Sms.getDefaultSmsPackage(this).equals(myPackageName)) {
            // App is not default.
            // Show the "not currently set as the default SMS app" interface
            View viewGroup = findViewById(R.id.not_default_app);
            viewGroup.setVisibility(View.VISIBLE);

            // Set up a button that allows the user to change the default SMS app
            Button button = (Button) findViewById(R.id.change_default_app);
            button.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    Intent intent =
                            new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
                    intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, 
                            myPackageName);
                    startActivity(intent);
                }
            });
        } else {
            // App is the default.
            // Hide the "not currently set as the default SMS app" interface
            View viewGroup = findViewById(R.id.not_default_app);
            viewGroup.setVisibility(View.GONE);
        }
    }
}

문자 백업 복구 앱에 대한 조언

문자 프로바이더를 쓸 수 있는 기능이 사용자가 기본 문자 앱으로 선택한 앱에 한정되기 때문에 순전히 문자를 백업하고 복구하게 설계된 기존 앱들이 안드로이드 4.4에서 문자를 복구하는 것이 현재 허용되지 않습니다. 문자 메시지를 백업하고 복구하는 앱은 기본 문자 앱으로 지정이 되어야 SMS 프로바이더에 메시지를 쓸 수 있습니다. 하지만 앱이 SMS 메시지를 받고 읽을 필요가 없다면 기본 문자 앱 설정을 유지해서는 안됩니다. 그래서 사용자가 여러분의 앱을 열어 일회용 복원 명령을 시작할 때 다음 설계된 기능적인 사용자 경험을 제공할 수 있습니다.
  1. 현재 기본 문자 앱의 패키지 네임을 질의 하고 저장합니다.
    String defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(context);
  2. 문자를 복원하기 위해 기본 문자앱을 사용자가 전환하게 요청합니다. (문자 프로바이더를 작성하기 위해서는 기본 문자 앱이 되어야 합니다.
    Intent intent = new Intent(context, Sms.Intents.ACTION_CHANGE_DEFAULT);
    intent.putExtra(Sms.Intents.EXTRA_PACKAGE_NAME, context.getPackageName());
    startActivity(intent);
  3. 모든 문자 메시지가 복원되면 (첫번째 절차에서 저장된) 기존에 서택된 기본 문자 앱으로 돌아가도록 요청합니다.When you finish restoring all SMS messages, request the user to change the default SMS app back to the previously selected app (saved during step 1).
    Intent intent = new Intent(context, Sms.Intents.ACTION_CHANGE_DEFAULT);
    intent.putExtra(Sms.Intents.EXTRA_PACKAGE_NAME, defaultSmsApp);
    startActivity(intent);

문자 앱 업데이트 준비하기

안드로이드에서 최고의 경험을 사용자에게 제공하기 위해 가능한 빨리 업데이트하길 격려합니다. 안드로이드 4.4의 변화를 컴파일하고 실험하기 위해 필요한 안드로이드 4.4를 위한 SDK  콤포넌트 곧 제공하겠습니다. 계속 개선하세요!