반응형

안녕하세요. Simple& Happy Dev입니다.

앞선 글에서 손전등 앱을 개발하기 위해 Camera2 API를 이용해서 카메라 플래시를 제어하는 방법을 적어보았는데, 쉽지 않으셨을 겁니다.
손전등 앱으로는 잘 사용하지 않는 방법이니 Camera2에 대해서 공부했다고 위안으로 삼으셔도 됩니다.

이번에 알려드리는 방법은 이전 방법들 대비해서 매우 간단한  Flashlight API를 이용하는 방법입니다.

Android 6.0 이전까지 플래시는 카메라 디바이스의 보조 장치이고, 손전등에 대해서 고려되지 않았습니다.
그로 인하여 Camera/Camera2 API로 구현 시 비효율적인 부분이 많이 있었습니다.

Android 6.0(Marshmello, API level 23)부터는 손전등을 고려해서 Flashlight API가 추가되었고, 또한 안드로이드 시스템 UI에서 손전등이 자체 내장이 되기 시작했습니다.

아래와 같이 퀵세팅(QuickSetting)에 가면 손전등 타일이 추가되어 있습니다.

Flashlight API는 매우 간단하면서 카메라 권한이 필요하지 않다는 장점이 있습니다. 반면에 Android 6.0 이상의 단말기에만 동작한다는 제약성도 있습니다.

[Flashlight API 이용한 손전등 앱]

Camera API와 Camera2 API를 이용한 손전등 앱에서는 AndroidManifest.xml에 카메라 권한(android.permission.CAMERA)이 명시되어 있지 않으면 Exception이 발생합니다.

Camera / Camera2 API의 openCamera()를 호출하면, 내부에서 permission check하는 루틴에서 에러를 리턴서 Exception을 발생하게 합니다.

특히, Camera2에서는 Annotation(@RequiresPermission)으로 인하여 빌드 전에 미리 오류에 대한 확인이 가능합니다. 

@RequiresPermission(android.Manifest.permission.CAMERA)
public void openCamera(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException {

openCameraForUid(cameraId, callback, handler, USE_CALLING_UID);
}

그런데, Flashlight API의 경우 Camera2에 추가된 것이지만, 사전에 openCamera() 하지 않고 사용할 수 있습니다.
그 결과 카메라 권한이 없어도 된 것입니다.

Flashlight API(setTorchMode) 이용한 손전등 앱에서 플래시가 동작하는 과정은 아래와 같습니다.

준비작업: 플래시 존재 여부 체크 - CameraManager 객체의 인스턴스 생성

플래시 켜기: setTorchMode(cameraId, true)

플래시 끄기: setTorchMode(cameraId, false)

매우 간단하기에 바로 소스로 들어가 보겠습니다.

[소스 설명]

레이아웃 (activity_main.xml) 
 
화면 한가운데 별 모양 버튼을 하나 추가 후 이걸 누르면 플래시가 켜지도록 할 예정입니다. 그리고, 한 번 더 누르면 버튼이 토글되어서 꺼지도록 하겠습니다. 별 모양 버튼은 SDK에 내장된 리소스를 사용하시면 됩니다. (앞쪽에 있는 글들과 동일함)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageButton
android:id="@+id/ibFlashOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_star_big_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>


자바 (MainActivity.java)

onCreate에서는 기기에 플래시가 지원하지 않으면 메시지 출력하고 3초 후 종료하도록 처리(delayedFinish())하도록 만들어 줍니다.
또한 별 모양 버튼에 대한 클릭 리스너를 만들어서 클릭 시 플래시를 켜주거나 꺼주고(flashlight()), 플래시 켜짐 상태에 따른 별 모양 이미지(켜짐: 노란색 별, 꺼짐: 흰색 별)를 변경하도록 처리해줍니다. (여기까지는 앞에 있는 부분과 공통)

Flashlight API(setTorchMode)를 사용하기 위해서 CameraManager 객체의 인스턴스를 획득합니다.

package com.example.help.nopermissionflashlight;

import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
private ImageButton mImageButtonFlashOnOff;
private boolean mFlashOn;

private CameraManager mCameraManager;
private String mCameraId;

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

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
Toast.makeText(getApplicationContext(), "There is no camera flash.\n The app will finish!", Toast.LENGTH_LONG).show();

delayedFinish();
return;
}

mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);

mImageButtonFlashOnOff = findViewById(R.id.ibFlashOnOff);
mImageButtonFlashOnOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
flashlight();
mImageButtonFlashOnOff.setImageResource(mFlashOn ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
}
});
}

private void delayedFinish() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 3500);
}

버튼을 눌러 flashlight()가 호출되면, CameraManager를 통해 카메라 id들을 가져온 후 필요한 카메라 특성을 구하고(getCameraCharacteristics), 원하는 조건(플래시 사용이 가능 & 화면 기준으로 뒤쪽에 있는 카메라)에 해당하는 카메라 id(주로 "0")를 구합니다.

이후 플래시 상태를 반전하기 위해서 mFlashOn을 토글시켜주고, setTorch()에 위에서 구한 카메라 id와 mFlashOn을 argument로 넘겨줍니다. setTroch()가 호출이 되면 mFlashOn 상태에 따라서 플래시가 켜지거나 꺼지게 됩니다.

void flashlight() {
if (mCameraId == null) {
try {
for (String id : mCameraManager.getCameraIdList()) {
CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
if (flashAvailable != null && flashAvailable
&& lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
mCameraId = id;
break;
}
}
} catch (CameraAccessException e) {
mCameraId = null;
e.printStackTrace();
return;
}
}

mFlashOn = !mFlashOn;

try {
mCameraManager.setTorchMode(mCameraId, mFlashOn);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

프로젝트 압축화일
NoPermissionFlashlight.zip


앱 동작 영상

 


기타정보

Flashlight API를 이용하게 되면 Flashlight를 독점적으로 소유할 수 없습니다.
이것의 의미는 Flashlight API를 사용하는 다른 앱에서 똑같이 Flashlight API를 호출하게 되면 그쪽으로 소유가 넘어간다는 의미입니다.

아래 테스트 영상과 함께 설명드리겠습니다. 영상에는 세 가지 손전등앱이 있습니다.
1. 시스템 UI인 QuickSetting의 손전등
2. Camera API로 만든 PermissionFlashlight
3. Flashlight API로 만든 NoPermissionFlashlight

[case 1]
PermissionFlashlight 앱에서 Flash를 켜고, QuickSetting의 손전등을 확인하면 아이콘이 On이지만 Dimming 된 상태입니다.
이것의 의미는 QuickSetting의 손전등에서 Flashlight를 소유해서 제어할 수 없다는 의미입니다.
그리고, QuickSetting에서 Dimming 된 손전등의 On 아이콘을 누르면, "카메라 앱에서 카메라 플래시를 사용 중이어서 손전등을 켤 수 없습니다."라는 토스트가 나옵니다. 이는 QuickSetting의 손전등에서 PermissionFlashlight 앱이 Camera API로 만들어져서 카메라 앱으로 간주하고 있습니다.

[case 2]
NoPermissionFlashlight 앱에서 Flash를 켜고, QuickSetting의 손전등을 확인하면 아이콘이 On 되어 있습니다.
이는 QuickSetting의 손전등에서 다른 앱에서 Flashlight API를 사용해서 On 시킨 것을 인지한 상태입니다.
그리고, QuickSetting에서 손전등의 On 아이콘을 누르면 Off 상태로 아이콘이 변경되면서 플래시가 꺼집니다.
이는 QuickSetting의 손전등으로 Flashlight 소유가 넘어가서 제어되고 있다는 것을 의미합니다.

그런데, NoPermissionFlashlight 앱으로 돌아오면 가운데 별 모양은 계속 On 상태로 남아있죠?
이건 왜 그럴까요? 정답은 NoPermisionFlashlight 앱에서는 다른 앱에서 Flashlight API를 사용해서 On 시킨 것을 인지하는 리스너가 구현되어 있지 않기 때문입니다. QuickSetting의 손전등처럼 리스너를 구현해주면 플래시가 꺼지면 콜백되어서 Off 상태 인지가 가능해서 아이콘 모양 변경이 가능합니다. 


이상으로 "손전등 앱 만들기 세 번째 - Flashlight API 이용방법"에 대해서 설명해 드렸습니다.
상황에 따라서 세 가지 방법을 적절히 이용하시면 손전등 앱을 구현하실 수 있을 겁니다.

조금이나마 도움이 되셨으면 아래 공감 버튼을 눌러주세요.
(If this article helps you, please press the button below.)

+ Recent posts