2016년 8월 3일 수요일

Android Studio - 주소록, SMS, 음성인식 연동하여 간단 메시지 전송 앱 만들기


주요 체크포인트

- Permission 획득
- 외부 APP 연동
  . 주소록 정보 가져오기
  . 음성인식으로 텍스트 정보 채우기
  . SMS 전송결과 Broadcast 를 이용하여 Intent

package com.example.ch4_contacts_sms;

import android.Manifest;
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.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.speech.RecognizerIntent;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    Button sendButton;
    Button voiceButton;
    Button contactButton;
    EditText phoneEdit;
    EditText contentEdit;

    boolean contactPermission;
    boolean smsPermission;
    boolean phonePermission;

    private final int REQUEST_FOR_CONTACT = 10;
    private final int REQUEST_FOR_VOICE = 20;
    private final int REQUEST_FOR_PERMISSION = 200;

    @Override    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendButton = (Button) findViewById(R.id.button_send);
        voiceButton = (Button) findViewById(R.id.button_voice);
        contactButton = (Button) findViewById(R.id.button_contacts);

        phoneEdit = (EditText) findViewById(R.id.edit_phone);
        contentEdit = (EditText) findViewById(R.id.edit_content);

        sendButton.setOnClickListener(this);
        voiceButton.setOnClickListener(this);
        contactButton.setOnClickListener(this);

        // api level 22까지는(5.1) manifest <uses-permission>으로 등록만 하면 실행되는 개발자 신고제        // 23(6.0) 부터는 개발자가 manifest에 아무리 등록했다고 하더라도 user가 환경설정에서 permission enable/disable 조절가능        // manifest에 등록했다고 끝이 아니라 코드에스 그 부분을 실행할 때 권한획득여부 체크 필수        contactPermission = smsPermission = phonePermission = false;
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)== PackageManager.PERMISSION_GRANTED){
            contactPermission = true;
        }
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS)== PackageManager.PERMISSION_GRANTED){
            smsPermission = true;
        }
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)== PackageManager.PERMISSION_GRANTED){
            phonePermission = true;
        }

        // 만약 disable로 되어있는 상태라면 runtime error 유발        // user에게 toast로 알려주고 끝내도 되지만, 강제로 permission을 조정할 수는 없으니        // permission 허가를 요청하도록하자        // user permission 허가를 하기 위해선 다시 환경설정으로 가기는 불편하니까        //  app에서 dialog permission 조정하도록 system dialog로 제공하자        if(!contactPermission && !smsPermission && !phonePermission){
            requestPermission();
        }
    }

    private void requestPermission(){
        // user에게 permission허용 dialog를 띄우는 역할        ActivityCompat.requestPermissions(this, new String[]{
                Manifest.permission.READ_CONTACTS,
                Manifest.permission.SEND_SMS,
                Manifest.permission.READ_PHONE_STATE        }, REQUEST_FOR_PERMISSION);
    }

    // requestPermission함수를 이용해서 유저에게 permission 조정 dialog를 띄웠다고 하더라도    // 여전히 유저가 거부했을 수도 있다.    // dialog를 띄웠다고 끝이 아니라 사후 추적해야 한다.    // requestPermission 함수에 의한 dialog 작업이 끝나는 순간 자동 호출    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == REQUEST_FOR_PERMISSION && grantResults.length>0){
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                contactPermission = true;
            }
            if(grantResults[1] == PackageManager.PERMISSION_GRANTED){
                smsPermission = true;
            }
            if(grantResults[2] == PackageManager.PERMISSION_GRANTED){
                phonePermission = true;
            }
        }
    }

    public void onClick(View v) {
        if(v==contactButton){
            if(contactPermission){
                // 주소록의 목록화면을 띄운다.                Intent intent = new Intent(Intent.ACTION_PICK,
                        Uri.parse("content://com.android.contacts/data/phones"));
                // 결과를 돌려 받아야 한다.                startActivityForResult(intent, REQUEST_FOR_CONTACT);
            }else{
                requestPermission();
            }
        }else if(v == voiceButton){
            // 외부 app연동시 activity intent를 발생시키는데            //  intent에 의해 구동될 activity가 없을 수도 있다.            // intent를 발생시키기 전에 반응할 component가 있는지 체크 해야 함.
            // PackageManager: app이 설치된 static(run time시 동작중이지 않은)정보 추출            // 폰에 설치된 전체 app 목록(app package==app의구분자 획득 가능)            // intent에 반응할 component 정보
            // ActivityManager: PackageManger와 거의 쌍으로 등장하는 class            //                : 설치된 app runtime시에 살아서 움직이고 있는 정보            // 시스템에 동작중인 모든 process 목록 - pid 획득, 프로세스 kill            // 시스템에 동작중인 service 목록            // 폰의 top을 차지하고 있는 activity 정보            PackageManager pm = getPackageManager();
            List<ResolveInfo> activities = pm.queryIntentActivities(
                    // ACTION_RECOGNIZE_SPEECH action코드에 반응하는 activity get                    new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
            if(activities.size() > 0){
                Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                // free form: 말하는것 고대로 text                // web search: 말하는것 web search                intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                        RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                // Dialog title 문자열                intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "본문을 말하세요");
                // 원한다면 언어도 고정가능하고 지정하지 않으면 폰의 기본 로케일을 따름
                startActivityForResult(intent, REQUEST_FOR_VOICE);
           }else{
                Toast t = Toast.makeText(this, "음성인식을 지원하지 않는 폰", Toast.LENGTH_SHORT);
                t.show();
            }
        }else if(v == sendButton){
            if(smsPermission && phonePermission){
                // 유저 폰 번호를 sms 발신자 번호로 세팅                TelephonyManager telephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
                String myNumber = telephonyManager.getLine1Number();

                // sent ack시 실행할 intent 의뢰 ==> PendingIntent                Intent intent = new Intent("SENT_SMS_ACTION");
                PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
                        0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

                // sms 발송                SmsManager smsManager = SmsManager.getDefault();
                smsManager.sendTextMessage(
                        phoneEdit.getText().toString(),
                        myNumber,
                        contentEdit.getText().toString(),
                        pendingIntent,
                        null                );
            }else{
                requestPermission();
            }
        }
    }

    // startActivityForResult에 의한 요청이 되돌아 올때 자동 호출    // requestCode:  intent를 발생시킨 곳에서 intent를 구분하기 위해서 지정하는 임의의 숫자    //     ex) startActivityForResult(intent, 10); 에서 '10'    // resultCode: intent에 의해 수행된 곳에서 결과를 되돌리기전에 상태를 표현하기 위한 값    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == REQUEST_FOR_CONTACT && resultCode == RESULT_OK) {
            // 주소록 목록에서 홍길동을 눌러 되돌아 왔다고 하더라도            // 홍길동의 전화번호가 넘어오지는 않는다.            // 홍길동을 식별하기 위한 식별자 값만 넘어온다.            // url 문자열로 url의 맨 마지막 단어가 식별자값이다.            String id = Uri.parse(data.getDataString()).getLastPathSegment();

            // id값을 조건으로 구체적으로 원하는 데이터를 요청 provider 이용            // select row의 집합객체            // cursor를 움직여서 row하나씩 선택하고 선택된 row column data 추출            Cursor cursor = getContentResolver().query(
                    // provider 식별자 uri                    ContactsContract.Data.CONTENT_URI,
                    // select column조건, 뽑고자 하는 데이터                    new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER},
                    // select where조건                    ContactsContract.Data._ID + '=' + id,
                    // where쿼리문에 (?)이 있을경우에 들어갈 args                    null,
                    // orderby 문자열                    null            );

            // row 선택            cursor.moveToFirst();   // 어차피 해당 id를 갖는 1개의 연락처만 선택됨
            // data추출 및 화면 출력            phoneEdit.setText(cursor.getString(0));
        }else if(requestCode == REQUEST_FOR_VOICE && resultCode == RESULT_OK){
            ArrayList<String> results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            contentEdit.setText(results.get(0));
        }
    }

    // sms send후에 시스템에 넘어오는 sen ack시에 실행될 receiver    BroadcastReceiver sentReceiver =
            new BroadcastReceiver() {
                @Override                public void onReceive(Context _context, Intent _intent) {
                    String msg="";
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            // 전송 성공 처리; break;                            msg="sms 전송 성공";
                            break;
                        case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                            // 일반적인 실패 처리; break;                            msg="sms 전송 실패";
                            break;
                        case SmsManager.RESULT_ERROR_RADIO_OFF:
                            // 무선 꺼짐 처리; break;                            msg="무선 꺼짐";
                            break;
                        case SmsManager.RESULT_ERROR_NULL_PDU:
                            // PDU 실패 처리; break;                            msg="pdu 오류";
                            break;
                    }
                    Toast t = Toast.makeText(MainActivity.this, msg,
                            Toast.LENGTH_SHORT);
                    t.show();
                }
            };

    protected void onResume() {
        super.onResume();
        registerReceiver(sentReceiver, new IntentFilter("SENT_SMS_ACTION"));
    };
    @Override    protected void onPause() {
        // TODO Auto-generated method stub        super.onPause();
        unregisterReceiver(sentReceiver);
    }

}

댓글 없음:

댓글 쓰기