본문 바로가기

프로그래밍/안드로이드

[안드로이드] 토스트, 비프음, 진동 출력 그리고 퍼미션(Permission)

※ 저는 안드로이드 프로그래밍 정복(김상형 著, 한빛미디어) 책을 이용해 공부하고 있으며

예제와 코드는 이 책을 통해 공부중임을 밝힙니다.

개인적인 공부를 하면서 정리한 형식이기 때문에 심각한 오류가 있을 수 있습니다. 피드백 주시면 정말 감사하겠습니다.


※ 안드로이드 프로그래밍 정복(289p~320p) 참조


[ 출력 파트 ] 


1. 토스트(Toast)


토스트(Toast) : 안드로이드가 시스템 차원에서 제공하는 작은 팝업 대화상자

불륨 조절, 문자 메시지 전달 알림시 잠깐 떴다가 사라지는 창이라고 보면 된다.

알림사항을 일방적으로 "전달"만 하며 포커스를 받을 수 없기 떄문에 사용자의 작업을 방해하지 않는다.


static Toast makeText(Context context, int resId, int duration)

static Toast makeText(Context context, CharSequence text, int duration)


다음은 배치 옵션이다.


void setGravity (int gravity, int xOffset, int yOffset)                    // 화면상 위치

void setMargin (float horizontalMargin, float verticalMargin)       // 여백

void setText (CharSequence s)                                                // 표시할 텍스트

void setDuration (int duration)                                               // 지속 시간

void setView(VIew view)




일단, Toast makeText의 인수들을 잘 살펴보자.


static Toast makeText(Context context, CharSequence text, int duration)


액티비티명.this를 전달하고, 메시지를 설정하고, 지속 시간을 결정한다.


꼭 마지막에 .show()를 붙여야 메시지가 출력된다.



실행 결과이다. 얼마나 깜찍한가




2. 비프음(Beep Sound)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/play1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SoundPool"
/>
<Button
android:id="@+id/play2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="AudioManager"
/>
</LinearLayout>


먼저 XML을 설정해주고..



위에 ddok.wave를 res / raw 폴더에 집어넣자(없으면 만들면 된다.)




3. 진동(Vibrator)



객체를 별도 생성할 필요 없이 액티비티의 메서드로 언제든지 사용 가능하다.


Object Activity.getSystemService (String name)


이러한 진동기능은 전역 객체인 "시스템 서비스"의 일종이다.


시스템 서비스는 이외에도 다양한 서비스들을 제공하고 있다.


 LAYOUT_INFALTER_SERVICE  // 전개자

AUDIO_SERVICE                 // 오디오 관리자

DOWNLOAD_SERVICE         // 다운로드 관리자

VIBRATOR_SERVICE            // 진동 관리자

LOCATION_SERVICE           // 위치 서비스 관리자

POWER_SERVICE               // 전원 관리자


Vibrator 클래스는 진동을 시작 및 중지하는 다음 메서드를 제공한다.



가상 에뮬레이터가 아닌 실제 핸드폰으로 해야 된다.


코드는 아래에 복사 붙여넣기 하도록 하겠다.


그러나 이 코드만으로는 핸드폰에서 제대로 작동되지 않는다.



이와 같이 오류가 발생한다.


이를 해결하기 위해서 퍼미션에 대해 알아보도록 하자.


4. 퍼미션(Permission)


위 에러코드 중에 위와 같은 에러를 찾아볼 수 있다.

즉, VIBRATE 퍼미션(승인)이 요구된다는 것이다.

진동은 핸드폰 내에 특정 부품을 사용하는 것이기 때문에 민감할 것이다.



manifest (영어로 명백한 이란 뜻이다) 를 열어 다음과 같이 코드를 기입한다.


<uses-permission adroid:name="android.permission.VIBRATE" />


진동 이외에도 배터리, 네트워크, 외부 저장 정치 사용 등과 같은 기능은  

보안상 문제나, 장비 부품을 사용해야 하기 때문에 퍼미션(permission, 승인)이 필요하다

(https://developer.android.com/reference/android/os/Vibrator.html)

다음 링크를 타고 들어가보면 (구글에서 제공하는 레퍼런스다. 유용하다고 한다.)



이렇게 퍼미션이 필요함을 밝히고 있다.



우리가 흔히 앱을 다운로드 받을 때 마주하는 장면이다.

이 퍼미션(permission)은 "신고제"이며 설치 시점에 한 번 사용자의 허가를 받는다.


하지만, 보안상 문제로 안드로이드 6.0부터 실행중에 사용자의 허가를 받는다.

인스타를 처음 깔고 카메라에 접근 하면 다음과 같은 창이 뜬다.



퍼미션은 두 가지 종류로 나뉜다.


1. 일반(Normal) 퍼미션 : 앱 외부의 데이터나 기능을 접근하지만 위험하지는 않다. 진동 모터, 플래쉬 같은 기능은 사용자 정보가 유출될 위험이 없을 것이다.


2. 위험(Dangerous) 퍼미션 : 사용자의 사적인 정보를 액세스하는 기능은 개인 정보가 유출될 수 있다. 예를 들어 주소록, 통화 목록, 현재 위치, 외부 저장 장치의 읽기 / 쓰기 등이 해당된다. 이런 위험한 퍼미션에 대해서는 실행중에 일일이 허가를 받아야 한다.

퍼미션 그룹 

퍼미션 

CONTACTS( 읽고 쓰고 얻고) 

READ_CONTACTS 

WRITE_CONTACTS

GET_CONTACTS

 PHONE(전화)

READ_PHONE_STATE

CALL_PHONE

READ_CALL_LOG

WRITE_CALL_LOG

ADD_VOICEMAIL

USE_SIP

PROCESS_OUTGOING_CALLS

 SMS(문자)

SEND_SMS

RECEIVE_SMS

READ_SMS

RECEIVE_WAP_PUSH

RECEIVE_MMS 

 STORAGE(저장)

READ_EXTERNAL_STORAGE

WIRTE_EXTERNAL_STROAGE

 LOCATION(위치)

ACCESS_FINE_LOCATION

ACCESS_COARSE_LOCATION

 CALENDAR(달력)

READ_CALENDAR

WRITE_CALENDAR 

 CAMERA(카메라)

CAMERA 

 MICROPHONE(녹음)

RECORD_AUDIO 

 SENSORS(센서)

BODY_SENSORS 


※ 프로젝트의 target SDK version 변경하기



File - Project Structure - Flavors - Target Sdk Version 변경


SDK 버전을 낮추면 위험 퍼미션에 대한 민감도를 낮출 수 있다.


실행중 퍼미션 요청 과정은 요약하면 검사 -> 요청 -> 결과 통보로 요약할 수 있다.


먼저 코드를 하나하나 살펴보자



주소록 읽기 버튼을 클릭할 때 아직 퍼미션(승인)이 있는지 없는지 확신할 수 없으므로


바로 outContact 메서드를 호출하지 않는다.


우선 checkSelfPermission (첫번째 빨간박스) 메서드로 READ_CONTACT 퍼미션을 유무를 조사한다.


퍼미션이 있다면 outContat()를 바로 호출해 주소록에 접근한다.


퍼미션이 없다면 requestPermission 메서드를 호출해 유저에게 허락을 요청한다.



사용자의 응답 결과는 onRequestPermissionResult 콜백으로 전달된다.





토스트(Toast).xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/shortmsg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="짧은 메시지"
/>
<Button
android:id="@+id/longmsg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="긴 메시지"
/>
<Button
android:id="@+id/count1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="카운트 연속 출력"
/>
<Button
android:id="@+id/count2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="카운트 연속 출력2"
/>
<Button
android:id="@+id/customview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="커스텀 뷰 표시"
/>
</LinearLayout>


토스트(Toast).java

public class ToastTest extends Activity {
Toast mToast = null;
int count;
String str;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.toasttest);

findViewById(R.id.shortmsg).setOnClickListener(mClickListener);
findViewById(R.id.longmsg).setOnClickListener(mClickListener);
findViewById(R.id.count1).setOnClickListener(mClickListener);
findViewById(R.id.count2).setOnClickListener(mClickListener);
findViewById(R.id.customview).setOnClickListener(mClickListener);
}

Button.OnClickListener mClickListener = new Button.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.shortmsg:
Toast.makeText(ToastTest.this, "잠시 나타나는 메시지",
Toast.LENGTH_SHORT).show();
break;
case R.id.longmsg:
Toast.makeText(ToastTest.this, "조금 길게 나타나는 메시지",
Toast.LENGTH_LONG).show();
break;
case R.id.count1:
str = "현재 카운트 = " + count++;
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(ToastTest.this, str, Toast.LENGTH_SHORT);
mToast.show();
break;
case R.id.count2:
str = "현재 카운트 = " + count++;
if (mToast == null) {
mToast = Toast.makeText(ToastTest.this, str, Toast.LENGTH_SHORT);
} else {
mToast.setText(str);
}
mToast.show();
break;
case R.id.customview:
LinearLayout linear = (LinearLayout)View.inflate(ToastTest.this,
R.layout.toastview, null);
Toast t2 = new Toast(ToastTest.this);
t2.setView(linear);
t2.show();
break;
}
}
};
}



비프음(Beep Sound).xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/play1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SoundPool"
/>
<Button
android:id="@+id/play2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="AudioManager"
/>
</LinearLayout>


비프음(Beep Sound).java

package lkcompany.exercise;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;


public class Exercise extends AppCompatActivity {
SoundPool mPool;
int mDdok;
AudioManager mAm;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercise);

mPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
mDdok = mPool.load(this, R.raw.ddok, 1);
mAm = (AudioManager)getSystemService(AUDIO_SERVICE);

findViewById(R.id.play1).setOnClickListener(mClickListener);
findViewById(R.id.play2).setOnClickListener(mClickListener);
}
Button.OnClickListener mClickListener = new Button.OnClickListener(){
public void onClick(View v){
MediaPlayer player;
switch (v.getId()){
case R.id.play1:
mPool.play(mDdok, 1, 0, -1, 0, 1);
break;
case R.id.play2:
mAm.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
break;
}
}
};
}


진동(Vibrator).xml


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

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#3F0099"
android:gravity="center"
android:text="Vibration 으로 진동 설정하기"
android:textColor="#FFFFFF" />

<Button
android:id="@+id/btn_alert"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="진동 시작" />

</LinearLayout>



진동(Vibrator).java

package [ 자신 이름에 맞게 수정 ];

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Vibrator;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;



public class [ 자신 이름에 맞게 수정 ] extends Activity implements
OnClickListener {

private Button button;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.[ 자신 이름에 맞게 수정 ]);

button = (Button) findViewById(R.id.btn_alert);

// 클릭 이벤트
button.setOnClickListener(this);
}

public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_alert:
Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
vibe.vibrate(500);
break;

default:
break;
}
}
}


퍼미션(Permission).xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/btnread"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="주소록 읽기"
/>
<Button
android:id="@+id/btnreset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="초기화"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/result"
android:textSize="16sp"
android:text="주소록"
/>
</LinearLayout>


퍼미션(Permission).java    

package lkcompany.exercise;

import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.TextView;

public class Exercise extends Activity {
TextView mResult;
final int READ_CONTACT_CODE = 0;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercise);
mResult = (TextView) findViewById(R.id.result);
}

public void mOnClick(View v) {
switch (v.getId()) {
case R.id.btnread:
tryOutContact();
break;
case R.id.btnreset:
mResult.setText("주소록");
break;
}
}

void tryOutContact() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) ==
PackageManager.PERMISSION_GRANTED){
outContact();
} else {
ActivityCompat.requestPermissions
(this, new String[]{Manifest.permission.READ_CONTACTS}, READ_CONTACT_CODE);
}
}

// @Override
public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults){
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

switch(requestCode){
case READ_CONTACT_CODE:
if(grantResults.length > 0 && grantResults[0] ==
PackageManager.PERMISSION_GRANTED) {
outContact();
}
}
}


void outContact() {
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(
ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
int nameidx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);

if (cursor.moveToNext()) {
mResult.setText(cursor.getString(nameidx));
} else {
mResult.setText("주소록이 비어 있습니다.");
}
cursor.close();
} }