안드로이드 키오스크(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

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

  1. Avatar

    안녕하세요 문의 좀 드리겠습니다.
    혹시 안드로이드 10 부터 권한승인을 받고 setCameraDisabled 함수로 카메라 기능을 막거나 하면 securityException 에러로
    is not a device owner or profile owner, so may not use policy
    라는 메세지가 뜨는데 10 대응을 하셨는지.. 궁금해서 여쭤봅니다.
    9이하에서는 정상작동 됩니다.

    1. mcsong

      안녕하세요. setCameraDisabled() 메서드가 10 부터 deprecated 되었는데요. API의 문서인 https://developer.android.com/reference/android/app/admin/DevicePolicyManager#setCameraDisabled(android.content.ComponentName,%20boolean) 을 읽어보시면..

      안드로이드 10(VERSION_CODES.Q) 기기에서 SDK 버전 타겟을 29 이하로 유지하면 이 API를 호출하여 카메라를 비활성화 할 수 있다고 합니다. 그리고, 타켓을 29로 올리면 SecurityException을 던지고, 30으로 올리면 그냥 무시해 버린답니다.

    2. mcsong

      추후에는 제조사 MDM 솔루션을 사용하던지.. 아니면, 10까지는 DPM을 사용해서 처리하면 되지만, 11(R)부터는 구글이 준비하고 있는 MDM 솔루션을 사용하는 형태로 개발하셔야 할 것으로 보입니다.

Leave a Comment

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다