All posts by mcsong

Software Engineer, 카산드라 완벽 가이드(http://www.yes24.com/24/goods/5847220) 번역

안드로이드 앱 테스트 서비스

안드로이드 앱을 개발하고 서비스하다 보면, 특정 기기에서만 문제가 발생하는 경우를 종종 볼 수 있다. 이 문제는 해당 기기가 없으면 재현하기도 힘들고, 결국 디버깅하기도 어렵다. 큰 회사의 경우에는 테스트 기기를 다양하게 보유하고 있어서 문제가 없지만, 필자의 경우처럼 개인 개발자들은 현실적으로 테스트 기기를 여러 대 구매하기가 어렵다. 그래서 찾아보니 앱을 온라인에서 쉽게 테스트할 수 있는 무료 서비스가 2개 정도 있고, 아주 유용하기에 이 온라인 테스트 서비스를 살펴보자.

1. SMAC 온라인 앱 테스트 도구

이 도구는 한국 모바일 산업 연합회라는 곳에서 운영하는 스마트 모바일 앱 개발자 지원센터에서 서비스하고 있다. 테스트 할 수 있는 단말기가 국내 안드로이드 기기 제조사, 소니, 그리고 애플의 몇 개의 기기에서 자신의 앱을 확인할 수 있다. 아래 링크로 사용할 수 있다.
https://apptest.appvillage.or.kr/service/index.do


– 앱 테스트 도구를 실행한 화면

위 화면은 온라인 앱 테스트 도구에서 삼성 갤럭시 S8 기기를 실행한 화면이다. 위아래 스크롤로 사용할 수 있는 기기를 확인할 수 있고, 원하는 기기를 선택하고 “시작하기” 버튼을 클릭하면 된다.

2. 삼성 리모트 테스트 렙(Remote Test Lab)

개발한 앱을 삼성의 모바일 제품군인 갤럭시(Galaxy), Z, 와치(Watch) 에서 테스트 할 수 있다. 갤럭시의 최신 제품인 Z 플립(Z Flip)이나 폴드(Fold) 제품은 가격이 고가이기에 개인 개발자가 앱을 테스트하기 위해서 구매하기 어렵기에 이 서비스는 개인 개발자들에게 매우 유용하다. 아래 링크로 연결할 수 있다.
http://developer.samsung.com/rtlLanding.do


– 삼성 리모트 테스트 렙의 갤럭시 탭 화면

위 화면은 갤럭시 탭에서 테스트할 수 있는 일부 기기를 보여준다. 이 외에도 갤럭시 노트 10, S9, S8, S7, S6, S5 및 탭 S4, S3 등을 테스트할 수 있게 지원한다. 위 화면에서 왼쪽의 Z를 선택하면 타이젠 기반의 앱을 확인할 수 있다. 그리고, 와치를 선택하면 갤럭시 와치, 기어, S3, 그리고 기어 핏 2에서 테스트할 수 있게 지원한다.

   
– 갤럭시 Z 플립에서 테스트하는 화면

위 화면은 갤럭시 Z 플립 기기를 실행한 화면이다. 첫 번째 화면은 플립을 접은 모습이고, 두 번째 화면을 플릭을 연 화면이다.

위 2개의 서비스에 대한 장/단점을 살펴보면 아래와 같다.

– SMAC 온라인 앱 테스트 도구는 테스트 도구를 실행하면 여러 기기(삼성 일부, LG, 그리고 소니 등)를 시작해서 테스트 할 수 있고, iOS/iPad도 지원하는 것이 강점이다. 그러나, 최신 안드로이드 기기를 지원하지 않는 것이 단점이다.

– 삼성 리모트 테스트 렙은 삼성에서 만들고 있는 다양하고 최신의 기기에서 테스트할 수 있게 지원하는 것이 강점이다. 그러나 매번 자바 웹 스타트 파일(jnlp)을 다운로드 하게 해서 여러 기기에서 테스트하기에는 좀 불편하다.

위 2가지의 앱 테스트 서비스를 사용하면, 개인 개발자가 많은 테스트 기기를 보유하지 않아도 테스트 및 디버깅을 할 수 있어서 아주 유용하다.

안드로이드 키오스크(Kiosk) 앱 개발 (3/3)

앞의 과정(안드로이드 키오스크 앱 개발 1/3, 안드로이드 키오스크 앱 개발 2/3)에서 안드로이드 앱을 기기의 키오스크 모드로 동작시키기 위한 프로그래밍 방법을 살펴봤다. 여기에서는 앞의 과정으로 만든 앱을 실 기기에 배포하는 방법을 살펴본다. 앱을 프로비저닝해서 실 기기에 배포하는 방법(버전별 지원 방법 확인)으로 NFC와 QR 코드로 앱을 설치하는 두 가지 방법이 널리 쓰이고, 여기에서 QR 코드를 사용하는 방법을 살펴보자.

전체 과정은 앱을 빌드하고, 빌드된 APK를 웹 서버에 업로드하고, 이 정보를 바탕으로 QR 코드를 생성하고, 마지막으로 QR코드를 사용해서 프로비저닝된 APK를 실제 기기에 설치하면 된다. 아래에서 개별 과정을 살펴보자.

1. 앱 빌드

릴리즈 버전의 앱은 아래의 2가지 방법으로 빌드하면 된다.

1.1 안드로이드 스튜디오의 Build > Generate Signed Bundle/APK 메뉴를 선택해서 빌드한다.

1.2 터미널(Terminal) 탭에서 아래의 명령으로 APK를 빌드한다.

./gradlew aR 

2. 웹 서버 및 ngrok 툴 설정

이제 빌드한 앱을 다운로드할 수 있는 엔드 포인트를 만들어보자.

2.1 웹 서버

아래 주소에서 간단한 바로 실행할 수 있는 자바 웹 서버를 다운로드 한다.
http://www.jibble.org/miniwebserver/

그리고, 아래의 명령으로 자바 웹 서버를 실행한다.

java -jar SimpleWebServer.jar

2.2 ngrok 툴 설정

아래 주소에서 ngrok 툴을 다운로드 받아서 설치한다.
https://ngrok.com/

내/외부에서 쉽게 도메인을 치고 들어올 수 있게 지원하는 툴이다. 이 툴의 설명을 보면, “secure introspectable tunnels to localhost”로 안전하게 로컬호스트로 터널링을 지원하는 도구라는 것을 알 수 있다.

아래이 명령으로 외부에서 http(s)://xxxxx.ngrok.io 도메인으로 접속할 수 있게 한다.

ngrok http 80 


– 웹 서버와 ngrok 툴 설정화면


– 웹 브라우저로 ngrok 툴이 설정한 주소로 접근한 화면

3. QR 코드 생성

이제 앱을 프로비저닝하는 QR 코드를 만들어 보자. APK 프로비저닝하기 위해서 QR 코드에 입력해야 하는 값은 아래와 같은 같다.

{
"android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME":"net.sjava.examples.kiosk/.KioskDeviceAdminReceiver",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION":"https://745b0d80.ngrok.io/app-release.apk",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM":"xxxx-xxxx-xxxxxx-xxxxxxxx_xxxx",
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION":true,
"android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED":true,
}

– QR 코드 입력값

위 코드에서 PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME 값은 패키지와 관리자 리시버 클래스 이름이다. PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION은 APK 파일을 다운로드 받을 주소이다. PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM은 APK의 CHECKSUM 값으로 아래의 명령을 사용해서 개발한 앱의 CHECKSUM 값을 구할 수 있다.

apksigner verify -print-certs app-release.apk | grep -Po "(?<=SHA-256 digest:) .*" | xxd -r -p | openssl base64 | tr -d '=' | tr -- '+/=' '-_'

PROVISIONING_SKIP_ENCRYPTION나 PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED은 이름에서 알 수 있듯이 암호화와 기존 시스템앱을 모두 사용할 수 있게 설정한다는 것이다.

위의 값으로 http://down-box.appspot.com/ 에서 위의 과정으로 만들어진 값을 사용해 아래와 같이 QR 코드를 쉽게 만들 수 있다.
위에서 샘플로 만들어본 QR 코드는 http://down-box.appspot.com/qr/GH1ayZg9 주소에서 확인할 수 있지만, 실제 기기에서는 동작하지 않는다.

4. QR 코드로 APK를 관리자 앱으로 설치

테스트할 수 있는 안드로이드 기기를 공장 초기화(Factory Reset)한다. 그리고, 첫 부팅 화면에서 시작 버튼이나 다른 버튼을 사용해서 안드로이드 기기를 시작하지 말고, 바탕 화면을 연속해서 6~7번 탭을 하면 QR 코드를 읽을 수 있는 앱을 실행하게 된다. 또는 안드로이드 7을 사용중인 경우에는 Wifi에 연결한 뒤에 QR 코드를 읽는 앱을 다운로드 해서 실행하는 경우가 있기에 조금 기다리면 QR 코드를 읽는 앱을 볼 수 있다.

이 앱을 사용해서 프로비저닝된 앱을 설치하면, 이 앱은 소유자 권한으로 설치가 되고 이 앱을 통해서 비교적 안전하게 키오스크 모드로 동작하는 앱을 배포할 수 있다.

Reference

https://developers.google.com/android/management/provision-device
https://github.com/googlesamples/android-testdpc

안드로이드 키오스크(Kiosk) 앱 개발 (2/3)

안드로이드 키오스크 앱 개발 (1/3)에서 안드로이드 앱을 키오스크 모드로 동작시키는 방법을 살펴봤다. 여기에서는 앞에서 살펴봤던 예제 프로젝트 앱에 관리자(Admin) 권한을 부여하는 방법과 소유자(Owner) 권한을 부여해서 삭제할 수 없고(예외로 공장 초기화로 삭제할 수 있음), 안드로이드 기기의 기능을 제한하는 방법을 살펴보자.

1. 디바이스 관리자 앱(Device Admin Apps)으로 등록

안드로이드 앱에서 기기를 관리(엔터프라이즈용 기능)할 수 있는 권한을 앱에 부여할 수 있고, 이 권한을 얻으면, DevicePolicyManager(https://developer.android.com/reference/android/app/admin/DevicePolicyManager) API를 사용해서 기기의 기능을 제한할 수 있다. 테스트 하는 기기에 등록된 앱을 살펴보면 아래와 같다.


– 디바이스 관리자 앱들

1.1 DeviceAdminReceiver 추가

앱을 기기 관리자 앱으로 등록하거나 삭제되는 경우에 시그널을 받을 수 있는 브로드캐스트 리시버를 추가한다.

public class KioskDeviceAdminReceiver extends DeviceAdminReceiver {
  static final String TAG = "SIMPLE_KIOSK";

  @Override
  public void onReceive(Context context, Intent intent) {
    Log.i(TAG, "onReceive: " + intent.getAction());
  }

  @Override
  public void onEnabled(Context context, Intent intent) {
    Toast.makeText(context, "Device admin enabled", Toast.LENGTH_SHORT).show();
  }

  @Override
  public void onDisabled(Context context, Intent intent) {
    Toast.makeText(context, "Device admin disabled", Toast.LENGTH_SHORT).show();
  }

  @Override
  public void onLockTaskModeEntering(Context context, Intent intent, String pkg) {
    Log.i(TAG, "onLockTaskModeEntering: " + pkg);
  }
}

– KioskDeviceAdminReceiver.java 파일

이 파일은 앱이 기기 관리자로 등록/해제 등의 이벤트를 받는 관리자 브로드캐스트 리시버이다. 그리고, 관리자 기능과 관련된 시그널을 받기 위해서 아래와 같이 위의 리시버를 AndroidManifest.xml의 의 하위 요소로 추가한다.

<receiver
    android:name=".KioskDeviceAdminReceiver"
    android:description="@string/app_name"
    android:label="@string/app_name"
    android:permission="android.permission.BIND_DEVICE_ADMIN">
    <meta-data
        android:name="android.app.device_admin"
        android:resource="@xml/device_admin_receiver"
        tools:ignore="DeviceAdmin" />
    <intent-filter>
        <action android:name="android.intent.action.DEVICE_ADMIN_ENABLED"/>
    </intent-filter>
</receiver>        

– AndroidManifest.xml에 KioskDeviceAdminReceiver 등록

KioskDeviceAdminReceiver도 다른 브로드캐스트 리시버와 동일하게 AndroidManifest.xml에 등록을 한다.

1.2 device_admin_receiver.xml 추가

이 파일을 /src/main/res/xml/ 폴더에 추가한다. 이 파일에는 앱이 관리자 권한을 얻게 되면, 사용할 정책을 정의한다. 개별 정책에 대한 내용은 DeviceAdminInfo 클래스 API에 정의되어 있다.

 <?xml version="1.0" encoding="utf-8"?>
<device-admin>
    <uses-policies>
        <limit-password />
        <watch-login />
        <reset-password />
        <force-lock />
        <wipe-data />
        <expire-password />
        <encrypted-storage />
        <disable-camera />
    </uses-policies>
</device-admin>   

– 데모 프로젝트의 device_admin_receiver.xml 파일

위 정책을 몇 개만 살펴보면, wipe-data는 로컬 스토리지를 포맷할 수 있는 권한을 가지게 되고, encrypted-storage는 스토리지를 암호화할 수 있고, disable-camera는 카메라 기능을 비활성화할 수 있다.

1.3 기기 관리자 권한 추가

기기에서 앱이 관리자 권한을 가지기 위해서는 일반 앱의 퍼미션 요청과 동일한 형태로 관리자 권한을 요청해야 한다.

  
  private void enableAdmin() {
    if (isDeviceAdminApp()) {
      return;
    }
    Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
    intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mAdminComponentName);
    // Start the add device admin activity
    startActivityForResult(intent, DEVICE_ADMIN_ADD_REQUEST);
  }

– MainActivity의 기기 관리자 권한 요청 코드

위 코드로 기기 관리자 권한을 요청할 수 있다.


– 기기 관리자 앱 활성화 화면

위 화면은 앱이 가지는 관리자 정책과 활성화를 요청하는 화면이고, 요청을 수락하면 앱은 관리자 권한을 가지고 동작한다.

  
// 일반 앱의 경우에는 isAdminApp, isOwnerApp의 값이 false이다.
2020-05-23 12:17:06.265 4712-4712/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isAdminApp: false
2020-05-23 12:17:06.266 4712-4712/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isOwnerApp: false

// 관리자 권한을 가지게 되면 isAdminApp의 값이 true인 것을 볼 수 있다.
2020-05-23 12:19:42.083 4976-4976/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isAdminApp: true
2020-05-23 12:19:42.084 4976-4976/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isOwnerApp: false

2. 앱에 소유자 앱 권한 부여

이제 앱에 소유자 권한을 부여하는 방법을 살펴보자. 여기에서는 소유자 앱의 권한을 주기 위해서 adb shell 명령을 사용할 것이다. 소유자 앱으로 테스트 하다가 문제가 생겨서 기기를 사용할 수 없게 되거나 공장 초기화(Factory Reset)를 해야 하는 경우가 발생할 수 있다. 그래서 아래와 같이 Applicaion 요소에 android:testOnly=”true” 를 추가해서 기기의 잠금으로 인한 문제를 방지한다.

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:testOnly="true"
    android:theme="@style/AppTheme">

– AndroidManifest.xml의 application 요소에 android:testOnly=”true”를 추가한 예

아래는 앱에 소유자 권한을 부여하고, 관리자 앱을 비활성화하는 방법이다.

  
# 소유자 앱 활성화
adb shell dpm set-device-owner net.sjava.examples.kiosk/.KioskDeviceAdminReceiver
# 관리자 앱 비활성화
adb shell dpm remove-active-admin net.sjava.examples.kiosk/.KioskDeviceAdminReceiver

// 소유자 앱 활성화 뒤
2020-05-23 12:34:14.450 4976-4976/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isAdminApp: true
2020-05-23 12:34:14.451 4976-4976/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isOwnerApp: true

// 관리자 앱 비활성화 뒤
2020-05-23 12:36:26.200 4976-4976/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isAdminApp: false
2020-05-23 12:36:26.201 4976-4976/net.sjava.examples.kiosk I/SIMPLE_KIOSK: isOwnerApp: false

만약, adb shell dpm set-device-owner 명령에 아래와 같은 에러를 보게 된다면, 설정(Settings) > 계정(Account)에 등록된 계정을 삭제하고 다시 실행하면 된다.

  
adb shell dpm set-device-owner net.sjava.examples.kiosk/.KioskDeviceAdminReceiver

java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device
at android.os.Parcel.createException(Parcel.java:2079)
at android.os.Parcel.readException(Parcel.java:2039)
at android.os.Parcel.readException(Parcel.java:1987)
at android.app.admin.IDevicePolicyManager$Stub$Proxy.setDeviceOwner(IDevicePolicyManager.java:8392)
at com.android.commands.dpm.Dpm.runSetDeviceOwner(Dpm.java:203)
at com.android.commands.dpm.Dpm.onRun(Dpm.java:115)
at com.android.internal.os.BaseCommand.run(BaseCommand.java:56)
at com.android.commands.dpm.Dpm.main(Dpm.java:41)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:338)
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.devicepolicy.DevicePolicyManagerService.enforceCanSetDeviceOwnerLocked(DevicePolicyManagerService.java:8647)
at com.android.server.devicepolicy.DevicePolicyManagerService.setDeviceOwner(DevicePolicyManagerService.java:7809)
at android.app.admin.IDevicePolicyManager$Stub.onTransact(IDevicePolicyManager.java:3270)
at android.os.Binder.execTransactInternal(Binder.java:1021)
at android.os.Binder.execTransact(Binder.java:994)

– adb shell dpm set-device-owner 에러 로그

아래 화면은 터미널에서 앱에 소유자 권한을 추가하고, 어드민 권한을 제거한 결과를 보여주는 화면이다.

– 소유자 권한 추가 및 해제 화면

3. 기능 제한

여기에서는 앱이 소유자 권한을 가지고 안드로이드 기기에서 기능을 제한하는 방법에 대해서 살펴보자. 간단하게 카메라 기능, 사용자의 일부 기능 제한, 그리고 앱 사용을 제한하는 방법에 대해서 살펴보자.

3.1 카메라 기능 제한

아래 코드로 기기 카메라 사용을 제한할 수 있다.

 
private void disableCamera(boolean disabled) {
  mDevicePolicyManager.setCameraDisabled(mAdminComponentName, disabled);
}

– 카메라 기능 제한

이 코드로 기기의 카메라 기능을 제한할 수 있다. 그리고, 사용자의 기능 제한은 3.2에서 확인할 수 있고, 많은 기능을 제한할 수 있는 것을 알 수 있다.

3.2 사용자 기능 제한

아래 코드로 사용자의 기능을 제한할 수 있다.

 
static final ArrayList mUserRestrictions = new ArrayList<>(
    Arrays.asList(
        UserManager.DISALLOW_SMS,
        UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
        //UserManager.DISALLOW_USB_FILE_TRANSFER,
        UserManager.DISALLOW_BLUETOOTH
    )
);

private void setUserRestrictions(boolean restricted) {
    for (String restriction : mUserRestrictions) {
        if (restricted) {
            mDevicePolicyManager.addUserRestriction(mAdminComponentName, restriction);
        } else {
            mDevicePolicyManager.clearUserRestriction(mAdminComponentName, restriction);
        }
    }
}

– 사용자 기능 제한

위 mUserRestrictions은 제한될 사용자의 기능을 정의했다. 코드에서 알 수 있듯이, UserManager에 제한할 기능이 정의되어 있고 필요한 기능은 확인해보면서 추가하면 된다.

3.3 앱 사용 제한

아래 코드로 설치된 앱 사용을 제한할 수 있다.

 
static final String[] mSuspendedPackageNames = {"com.twitter.android",
    "com.facebook.katana",
    "com.google.android.apps.nbu.files"
};

private void setPackagesSuspended(String[] packageNames, boolean suspended) {
    if(Build.VERSION.SDK_INT >= 24) {
        mDevicePolicyManager.setPackagesSuspended(mAdminComponentName, packageNames, suspended);
    }
}

– 앱 사용 제한

위 코드에서 mSuspendedPackageNames는 사용을 제한할 앱 목록을 가지고 있고, DevicePolicyManager의 setPackagesSuspended() 메서드를 호출해서 앱 사용을 제한할 수 있다. 더불어 DevicePolicyManager의 setApplicationHidden() 메서드로 앱 자체를 숨겨버릴 수도 있다.

이 프로젝트의 소스는 https://github.com/mcsong/SimpleKioskDemo에서 확인할 수 있다.


– 소유자 권한을 가진 앱으로 몇 개의 앱과 카메라 기능을 비활성화한 예제

위에서 살펴본 기능 외에도 많은 기능을 제한할 수 있다. 소유자 권한으로 안드로이드 API가 제공하는 기능 제한은 DevicePolicyManager 클래스에서 확인할 수 있다.

지금까지 안드로이드 앱을 키오스크 모드로 동작하고, 소유자 권한을 획득해서 안드로이드 기기를 온전히 키오스크 기기나 특정 용도에 맞게 사용할 수 있는 방법을 살펴봤다. 안드로이드 기기를 관리하는 방법으로 제조사에서 제공하는 MDM 솔루션(삼성 Knox나 LG Gate)을 사용할 수 있는데, MDM 솔루션을 사용하면 위의 기능에 더불어 제조사에서 자신들의 기기만을 위해서 추가한 기능도 관리할 수 있어서 조금 더 스트릭하게 관리할 수 있다. 개인적으로 몇 개의 MDM 솔루션으로 작업을 해 본 결과, 지금까지는 삼성 Knox가 가장 편리하고 기능적으로도 우수하다.

Reference

https://developer.android.com/guide/topics/admin/device-admin
https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode

안드로이드 키오스크(Kiosk) 앱 개발 (1/3)

이제 우리는 일상에서 많은 키오스크(Kiosk) 기기를 사용한다. 맥XXX 햄버거 체인점에서 주문하는 기기, 공항에서 층별 정보나 항공사 위치를 확인할 수 있는 기기, 회사 로비에 인포 데스크 없이 방문자를 확인할 수 있는 기기 등이 우리가 주변에서 쉽게 사용하고 있는 키오스크 기기이다. 이 기기 또한 우리가 널리 사용하고 있는 윈도, 리눅스, 안드로이드, IOS 등을 사용한다. 먼저 키오스크의 정의를 살펴보면 아래와 같다.

인터랙티브 키오스크(interactive kiosk)는 특수한 하드웨어와 소프트웨어를 갖춘 단말기의 하나로, 커뮤니케이션, 상업, 엔터테인먼트, 교육을 위한 정보 및 애플리케이션에 대한 접근 권한을 제공한다.

안드로이드 또한 키오스크 기능을 개발할 수 있게 API를 제공하기에, 키오스크 앱을 개발하는 방법과 앱이 키오스크 앱으로 동작하는데 필요한 고려사항에 대해서 세 파트(1/3, 2/3, 3/3)로 나눠서 살펴본다.

우선 키오스크 앱을 개발하기 전에 앱이 획득할 수 있는 권한에 대해서 살펴보자. 안드로이드 앱은 크게 시스템 앱과 일반 앱으로 구분할 수 있고, 안드로이드 제조사나 제조사 파트너의 경우에만 자신의 앱을 시스템 앱으로 배포할 수 있기에, 여기서는 일반 앱을 대상으로 한다. 안드로이드 앱의 권한은 일반 앱 -> 관리자(Admin) 앱 -> 소유자(Owner) 앱의 순으로 안드로이드 기기를 제어할 수 있는 권한을 가지게 된다.

일반 앱: 일반적인 방법으로 안드로이드 기기에 설치한 앱이다.
관리자(Admin) 앱: 일반 앱에서 사용자의 권한 요청과 수락의 과정으로 관리자(Admin) 권한을 가지는 앱이 될 수 있다. 관리자 권한은 다음에 살펴보겠지만, XML 정책 파일에 필요한 권한을 명시해서 권한을 요청한다. 이 과정은 일반 앱의 퍼미션 요청 및 수락 과정과 비슷하다.
소유자(Owner) 앱: 소유자 앱은 삭제할 수 없고, 관리자 권한을 포함해서 안드로이드 기기의 기능 및 앱을 제한할 수 있는 막강한 권한을 가진다.

일반 앱에서 관리자 권한을 요청해서 관리자 앱이 될 수 있지만, 소유자 앱의 경우에는 프로그래밍으로 권한을 얻을 수가 없다. 일반 앱이 소유자 앱이 되는 방법으로 2가지가 있는데 첫 번째가 제조사에서 제공하는 MDM 솔루션을 사용하는 방법이다. 이 경우에는 제조사가 MDM 솔루션을 제공해야 하고, 제공하는 MDM 솔루션을 무료 또는 구매하면 된다. 두 번째 방법은 APK를 프로비저닝(Provisioning)해서 QR 코드나 NFC를 사용해서 앱에 설치하는 방법이 있다. 여기에서 살펴보는 방법은 APK의 QR 코드를 만들고 앱에 소유자 앱으로 설치하는 방법을 살펴볼 것이다. 그리고, APK를 프로비저닝하는 방법은 https://developers.google.com/android/management/provision-device#provisioning_methods 에서 확인할 수 있다.

먼저 일반 앱으로 키오스크 모드 앱을 개발해 보자.

1. 키오스크 모드 시작 및 중지

키오스크 모드는 Activity의 startLockTask() 메서드를 호출해서 시작하고, 중지는 stopLockTask() 메서드를 호출하면 된다.

mStartLockButton.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    LockUtil.lock(MainActivity.this);
    startLockTask();
  }
});

mEndLockButton.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    LockUtil.unLock(MainActivity.this);
    stopLockTask();
  }
});

– MainActivity의 키오스크 시작 및 중지 코드

위 코드는 2개의 버튼으로 키오스크 모드를 시작하고 중지하는 것을 볼 수 있고, LockUtil 클래스는 키오스크 모드를 시작했다는 정보를 유지한다. 이 정보를 유지해서 안드로이드 기기가 재시작하는 경우에 바로 MainActivity를 실행시켜서 키오스크 모드를 시작할 수 있게 한다.


– 키오스크 시작화면

일반 앱으로 키오스크 모드를 실행하면, 위 화면과 같은 다이얼로그를 보게 되고 확인을 클릭해야 키오스크 모드로 동작한다. 키오스크 모드로 동작시키기 위해서 앱을 실행해서 매번 확인을 클릭해야 한다면 유지 및 관리 이슈가 클 것이다. 이 이슈는 앱을 소유자 권한으로 실행하고 키오스크 모드를 시작하면 이 이슈를 해결 할 수 있다.

2. 부팅 완료(BOOT_COMPLETED) 브로드 캐스트 추가

브로드 캐스트는 안드로이드 기기가 부팅을 완료한 후에 바로 키오스크 모드로 잠겨야 할 필요가 있는 경우에 사용할 수 있다.


    
        
    

– AndroidManifest.xml에 BootCompleteReceiver 추가

위 코드는 BOOT_COMPLETED 브로드 캐스트를 받아서 처리하는 클래스(BootCompleteReceiver)와 속성에 대해서 정의하고 있다. android:directBootAware=”true”의 경우에는 사용자가 잠금을 해제하지 않고도 실행할 수 있게 한다.

다음으로 BootCompleteReceiver를 살펴보자.

public class BootCompleteReceiver extends BroadcastReceiver {
  static final String TAG = "SIMPLE_KIOSK";

  @Override
  public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    Log.i(TAG, "onReceive: " + action);
    if(Intent.ACTION_BOOT_COMPLETED.equals(action)) {
        startActivity(context);
    }
  }

  static void startActivity(Context context) {
    Intent i = new Intent(context, MainActivity.class);
    // For Android 9 and below
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
      i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      context.startActivity(i);
      return;
    }
    // Android 10 ?
  }
}

- BootCompleteReceiver 코드

위 코드는 부팅을 완료하면, 안드로이드 OS가 ACTION_BOOT_COMPLETED 액션과 함께 BootCompleteReceiver를 호출한다. 이 액션이 들어온 경우에 MainActivity를 실행하는 것을 알 수 있다. 안드로이드 10의 경우에 백그라운드로 동작하는 컴포넌트에서는 액티비티를 실행할 수 없기에, 현재는 안드로이드 9 및 이하의 버전에서만 안드로이드 시작 후에 MainActivity를 실행해서 바로 키오스크 모드로 동작하게 할 수 있다. 안드로이드 10 버전은 다음에 살펴보도록 하겠다.

아래 영상은 안드로이드 8.1.0 에뮬레이터에서 일반 앱으로 키오스크 모드가 어떻게 동작하는 지를 확인할 수 있고, 안드로이드를 재시작한 뒤에 앱이 자동으로 키오스크 모드로 진입하는 것도 확인할 수 있다.


이 프로젝트의 소스는 https://github.com/mcsong/SimpleKioskDemo에서 확인할 수 있다.

Reference

- https://ko.wikipedia.org/wiki/인터랙티브_키오스크
- https://developer.android.com/guide/topics/admin/device-admin
- https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode

안드로이드 기기에서 앱 화면 캡처를 방지하는 방법

안드로이드 기기에서 간단하게 앱의 화면을 캡처하는 방법으로 전원 키 + 볼륨(소리) 낮춤 키를 동시에 눌러서 화면을 캡처한다. 보통은 이런 액션을 막을 필요는 없지만, 간혹 화면 캡처를 막아달라는 요청도 있다. 그래서 간단하게 화면 캡처를 방지하는 방법을 살펴보자.

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.WindowManager;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 화면 캡처 방지
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
        WindowManager.LayoutParams.FLAG_SECURE);

    setContentView(R.layout.activity_main);
  }
}

– 화면 캡처를 방지하는 코드

위의 주석에서 보면 알겠지만, 간단하게 Window에 WindowManager.LayoutParams.FLAG_SECURE 플래그를 추가해서 쉽게 화면 캡처를 방지할 수 있는 것을 알 수 있다.

    
– 화면 캡처 방지 코드 적용된 화면

왼쪽 화면은 캡처 방지 코드를 추가하지 않은 화면이고, 오른쪽은 화면 캡처 방지 코드를 추가한 화면으로, 최근 앱 목록에서도 앱의 최근 화면을 볼 수 없다.