Tag Archives: DevicePolicyManager

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