Android Management APIで適当にAndroid Enterpriseで遊ぶ

# はじめに

別記事で書いたTest DPCとは別に、[Android Management API](https://developers.google.com/android/management/introduction?hl=ja)と[Android Device Policy](https://play.google.com/work/apps/details?id=com.google.android.apps.work.clouddpc)アプリを用いて、遠隔でデバイス制御を試す手順について適当に記載する。
なお、大体の記事で「適当」と表現しているのは「割と裏どりとかせずに、やってできたかもしれないのでとりあえず書いてみた」程度のいい加減なものである。

[公式ドキュメント](https://developers.google.com/android/management/reference/rest?hl=ja)を元にブラウザポチポチでやる手順を記載しているが、おおむね同じ事は[Android Management API QuickStart](https://colab.research.google.com/github/google/android-management-api-samples/blob/master/notebooks/quickstart.ipynb)からcolaboratoryを使ってやる方法も公開されている。
また、この手順は「とりあえずやってみる」とか「商用EMM利用時のトラブルシュート」を主に記載しているので、自力でMDMを作りたいのであれば[そういう事を書かれている方のサイト](https://zenn.dev/yusukeiwaki/articles/b8fb86d5790df9)を見た方がいいかもしれない。

# 1. 用意するもの

- 適当なGoogleアカウント
    - まだEMM登録されていないもの
        - [ここ](https://play.google.com/work/adminsettings)にアクセスして「managed Google Play ストアへのアクセス権限が会社からまだ付与されていません。」と出れば良い
        - 既に組織が作成されているが身に覚えがないというのなら組織削除していいかも、知らんけど
- Google CloudのプロジェクトID
    - 新規に作れ
    - 作成したプロジェクトのIDを控えておく
- とりあえずブラウザは普段使っていないものか、シークレットモードにしておくといいかもしれない
    - 普段使いのGoogleアカウントでログインされていると面倒なので

# 2. サインアップURLの生成とGoogleアカウントの紐づけ

- [Android Management API - signupUrls - Create](https://developers.google.com/android/management/reference/rest/v1/enterprises/create?hl=ja)にアクセスする。
- 右に表示される「Try this method」の各項目を入力する
    - callbackUrl
        - この後の手順で、トークンを埋め込んで自動遷移するURL
        - おすすめはColaboratory手順でも使われている「https://storage.googleapis.com/android-management-quick-start/enterprise_signup_callback.html」が楽
        - 適当なURLでもいい（実在しないものとかアクセスできないものでも可能）
    - projectId
        - 項1で作成したプロジェクトID
    - Credentials
        - 両方チェックを入れる
- 「EXECUTE」をクリックする
    - アカウントの選択では、項1で準備したGoogleアカウントを利用する（以降同様）
    - 初回はおそらくアクセスリクエストが出てくるので許可する
- 結果が返ってくる
    - ステータスが200、JSONでnameとurlが返ってくる
    - このnameは後で使うので控えておく（signupUrlName）

```json:signupurl-create-result
{
  "name": "signupUrls/a34251889812",
  "url": "https://play.google.com/work/adminsignup?token=VGFzaGlrYSBuaSBrYWtvIGtha3VzdWJla2kgbmV0YSB3byBvbW9pa2tpcmkga2FpdGV0YWtlZG8gYW5tYXJpIGRlY29kZSBzaGluYWlkZSBuZQ"
}
```

- urlを踏む
    - 「Androidを仕事に活用」とページが出てくるので「スタートガイド」をクリックする
    - 「ドメイン名またはお店やサービスの名前」は適当に
    - 「企業向けモバイル管理（EMM）プロバイダ」に自信のプロジェクトIDが表示されていることを確認して「次へ」
- 連絡先情報
    - 最後の「同意」のチェックだけ入れて「確認」
- 設定完了
    - 「登録を完了」をクリックすると、「callbackUrl」で指定したURLに引数付で飛ばされる
- トークンの保存
    - おすすめURLを使った場合は、画面に表示されるのでコピペ
    - 適当なURLの場合は、アドレス欄に「enterpriseToken」の引数が付いているので、それを控えておく

# 3. 組織を作る

ここでいう「組織」は、これから作る設定や、デバイスを登録させる先となるグループといったようなAndroid Management APIにおける管理グループと考えていいかもしれない。

- [Android Management API - Enterprise - Create](https://developers.google.com/android/management/reference/rest/v1/enterprises/create?hl=ja)にアクセスする。
- 右に表示される「Try this method」の各項目を入力する（明示していない箇所はデフォのままでいい）
    - enterpriseToken
        - 項2の最後で控えたやつ
    - projectId
        - 項1で控えたやつ
    - signupUrlName
        - 項2の途中で控えたjsonの中のname
- 「EXECUTE」をクリックする
    - 前回同様
- 結果が返ってくる
    - ステータスが200、JSONでnameが返ってくる
    - このnameは後で使うので控えておく（enterpriseName）

```json:enterprise-create-result
{
  "name": "enterprises/LC01entname4"
}
```

# 4. ポリシーを作る

デバイスに適用される設定・制限を作成する

- [Android Management API - policies - patch](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies/patch?hl=ja)にアクセスする。
- 右に表示される「Try this method」の各項目を入力する（明示していない箇所はデフォのままでいい）
    - name
        - ポリシー名
            - 「enterprises/{enterpriseId}/policies/{policyId} 形式」で指定
            - 項3の例を用いると「enterprises/LC01entname4/policies/policy1」とか「enterprises/LC01entname4/policies/default」
                - Colaboratoryの例だとpolicy1
                - defaultにすると、次項のenrollmentTokensの作成の時にpolicyNameの設定を省略できるらしい
                - 以降は末尾を「default」にしたものとして記載する
    - Request body
        - 作成する設定をJSON形式で記載したもの
        - 項4aで解説
- 「EXECUTE」をクリックする
    - 前回同様
- 結果が返ってくる
    - ステータスが200、JSONで適用した設定に追加でバージョンとかが入って帰ってくる
        - バージョンは、この設定が何回更新されたかの値

## 4a. ポリシーの中身についてざっくりと

Colaboratoryの例では以下の通りとなっている。

```JSON:policy-sample-colaboratory
{
  "applications": [
    {
      "packageName": "com.google.samples.apps.iosched",
      "installType": "FORCE_INSTALLED"
    }
  ],
  "advancedSecurityOverrides": {
    "developerSettings": "DEVELOPER_SETTINGS_ALLOWED"
  }
}
```

- applicationはアプリの設定を定義する
    - この例だと、[Google I/O 2019](https://play.google.com/work/apps/details?id=com.google.samples.apps.iosched)アプリを[「自動的にインストールし、削除を禁止する」](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies?hl=ja#InstallType)設定となっている
- advancedSecurityOverridesはシステムのセキュリティ設定を定義する
    - この例だと[開発者向けオプションとセーフブートが許可](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies?hl=ja#DeveloperSettings)される


なお結果は以下の通り返ってくる

```json:policies-patch-result
{
  "name": "enterprises/LC01entname4/policies/default",
  "version": "1",
  "applications": [
    {
      "packageName": "com.google.samples.apps.iosched",
      "installType": "FORCE_INSTALLED"
    }
  ],
  "advancedSecurityOverrides": {
    "developerSettings": "DEVELOPER_SETTINGS_ALLOWED"
  }
}
```

# 5. 加入トークンの作成

- [Android Management API - enrollmenttokens - create](https://developers.google.com/android/management/reference/rest/v1/enterprises.enrollmentTokens/create?hl=ja)にアクセスする。
- 右に表示される「Try this method」の各項目を入力する（明示していない箇所はデフォのままでいい）
    - parent
        - enterpriseIdを入れる
    - Request body
        - 加入トークンの有効期限はデフォルト値が1時間なので、これを無期限で永続的に使えるようにduration設定を記載する
            - 有効期限は秒単位で指定だが、MAXが10,000年なので10000x365x24x60x60の数値にしてみた。
        - 加入トークンを一回使い捨てとしたい場合は[oneTimeOnly](https://developers.google.com/android/management/reference/rest/v1/enterprises.enrollmentTokens?hl=ja#EnrollmentToken)を付加する
        - 適用するポリシーをdefault以外にしたい場合はpolicyNameを付与する
        - Work Profileの利用を制限する場合はAllowPersonalUsageにPERSONAL_USAGE_DISALLOWEDを指定する

```json:enrollmenttokens-sample
{
  "duration": "315360000000s"
}
```

- 「EXECUTE」をクリックする
    - 前回同様
- 結果が返ってくる
    - ステータスが200、JSONで適用した設定にいろいろと値が入って帰ってくる。
        - name
            - このトークン名
            - トークンを執行させるときとかに使う
            - listで一括取得できるので特に控えておく必要はない
        - value
            - 加入トークン
            - 項5aで補足
        - duration
            - 投げ込んだ設定値
        - expirationTimestamp
            - durationを元に決定されたこのトークンの有効期限
        - qrCode
            - 加入用QRコードを作成するための文字列
            - エスケープがちょっと邪魔
            - 項5aで補足

結果

```json:enrollmenttokens-create-result
{
  "name": "enterprises/LC01entname4/enrollmentTokens/Q4p5gP-y33WYFWKcvJH4w_8VURfEzxxQnNl6vdGJ9f0",
  "value": "5oud5ZWT44CB5p2J44GV44G+44CC44Gh44KH44Gj44Go6Iqx57KJ6YeP5oqR44GI44Gm44GP44KM44G+44Gb44KT44GLCg",
  "duration": "315360000000s",
  "expirationTimestamp": "9999-12-31T23:59:59.999999999Z",
  "qrCode": "{\"android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME\":\"com.google.android.apps.work.clouddpc/.receivers.CloudDeviceAdminReceiver\",\"android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM\":\"I5YvS0O5hXY46mb01BlRjq4oJJGs2kuUcHvVkAPEXlg\",\"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION\":\"https://play.google.com/managed/downloadManagingApp?identifier=setup\",\"android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE\":{\"com.google.android.apps.work.clouddpc.EXTRA_ENROLLMENT_TOKEN\":\"5oud5ZWT44CB5p2J44GV44G+44CC44Gh44KH44Gj44Go6Iqx57KJ6YeP5oqR44GI44Gm44GP44KM44G+44Gb44KT44GLCg\"}}"
}
```

## 5a. 加入用QRコードについて

QRコードの内容について詳細は[別記事の該当項目](https://qiita.com/garupon/items/844770b1eb48ac004ced#qr%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E6%9B%B8%E5%BC%8F%E4%B8%80%E9%83%A8-zero-touch%E3%81%AEcusom-dpc%E3%81%AB%E6%B5%81%E7%94%A8%E5%8F%AF%E8%83%BD)を参照

上記の結果を成形したものが以下

```JSON:enrollment-qr-sample
{
    "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME": "com.google.android.apps.work.clouddpc/.receivers.CloudDeviceAdminReceiver",
    "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM": "I5YvS0O5hXY46mb01BlRjq4oJJGs2kuUcHvVkAPEXlg",
    "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION": "https://play.google.com/managed/downloadManagingApp?identifier=setup",
    "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE": {
        "com.google.android.apps.work.clouddpc.EXTRA_ENROLLMENT_TOKEN": "5oud5ZWT44CB5p2J44GV44G+44CC44Gh44KH44Gj44Go6Iqx57KJ6YeP5oqR44GI44Gm44GP44KM44G+44Gb44KT44GLCg"
    }
}
```

「android.app.extra.PROVISIONING」は省略

- DEVICE_ADMIN_COMPONENT_NAME
    - 共通項目で原則不変
    - [Android Device Policy](https://play.google.com/work/apps/details?id=com.google.android.apps.work.clouddpc)アプリを用いるという宣言
- DEVICE_ADMIN_SIGNATURE_CHECKSUM
    - 共通項目で原則不変
    - 上記パッケージの署名ハッシュ値
- DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION
    - 共通項目で原則不変
    - 上記パッケージをどこからダウンロードするかの指定
- ADMIN_EXTRAS_BUNDLE
    - com.google.android.apps.work.clouddpc.EXTRA_ENROLLMENT_TOKEN
        - 環境により可変、というか項5で発行した加入トークン（value）が入る

デフォルト（上記のまま）のQRコードでFully Managedとして加入すると、プリインアプリの多くが非表示・アンインストールされることに注意

# 6. デバイス加入

加入トークン作成時の設定と加入方法によって、Fully、COMP、Work Profileが変わる

## 6a. QRコードで加入する

- 項5で取得したqrCodeの文字列を成形してQRコードにする
- 初期化されたデバイスの画面を7連打し、QRコードを読ませる
- あとは画面に従う

## 6b. DPC Identidierで加入する

- 初期化されたデバイスの画面に従い、Googleアカウント入力画面まで進める
- Googleアカウントの欄に「afw#setup」と入力
- QRコードを読み込む画面が出てくるので、以下のいずれかを行う
    - 項5のvalueをQRコードにしたものを読む
    - 項6aと同じQRコードを読む
    - メニューから手動入力を選択して項5のvalueを入力する

## 6c. Work Profileで加入する

バージョンとか機種で変わる可能性あり

- 設定アプリから、Googleを開く
- セットアップと復元の中にある、仕事用プロファイルの設定を開く
- 画面の指示に従って進む
- QRコードを読み込む画面が出てくるので、以下のいずれかを行う
    - 項5のvalueをQRコードにしたものを読む
    - 項6aと同じQRコードを読む
    - メニューから手動入力を選択して項5のvalueを入力する

# おまけ1. 主にManagement Google Playとかアプリ周りに関わるPolicyの小ネタ

- playStoreModeのデフォルトはWHITELIST、applicationsで設定したアプリのみがストアに表示される
    - BLACKLISTに設定すると、Managed Google PlayアカウントでPlay Storeから無料アプリを自由に入れられるモードになる。
        - applicationsでinstallTypeをBLOCKEDにしたアプリはインストールできないし、有料アプリとアプリ内課金は不可
- appAutoUpdatePolicyの設定やapplicationsの各アプリにautoUpdateModeを設定することでアプリのアップデートタイミングを操作できる。
    - appAutoUpdatePolicyはPlayStoreのアップデート設定を固定化。ただしdeprecated
        - これに限らずAndroidはdeprecatedが多くてちょっと辛い
- systemUpdateでAndroidのバージョンアップの制御ができる
    - アップデートを完全に抑止できるものではなく、一定期間止める（遅延させる）系と、特定時間のアップデートを止める系
    - 完全にアップデートを止めてバージョン固定したければ、そういう設定に対応した機種を選ばないと実現できぬ
- デバイスにインストールされているアプリの情報を抜く場合はstatusReportingSettingsとその子設定が必要
    - applicationReportsEnabled とかapplicationReportingSettings
    - 一気に出力量が増えるので、台数が多いならスクリプト組んでおいたほうがいいかも知れない

# おまけ2. デバイス情報の取得

- 全台引っこ抜く場合
    - [Android Management API - devices - list](https://developers.google.com/android/management/reference/rest/v1/enterprises.devices/list?hl=ja)にアクセス
    - 右に表示される「Try this method」の各項目を入力する（明示していない箇所はデフォのままでいい）
        - parent
            - enterpriseId
- 指定した一台を引っこ抜く場合
    - [Android Management API - devices - get](https://developers.google.com/android/management/reference/rest/v1/enterprises.devices/get?hl=ja)にアクセス
    - 右に表示される「Try this method」の各項目を入力する（明示していない箇所はデフォのままでいい）
        - name
            - deviceName
            - デバイス名を知るためにはまず全台引っこ抜くところから


- 「EXECUTE」をクリックする
    - 前回同様
- 結果が返ってくる

# おまけ3. デバイスのパスワード変更

パスワードは変更しかできず、パスワードクリア（パスワード設定がされていない状態にすること）はできないっぽい

- [Android Management API - devices - issueCommand](https://developers.google.com/android/management/reference/rest/v1/enterprises.devices/issueCommand?hl=ja)にアクセスする。
- 右に表示される「Try this method」の各項目を入力する（明示していない箇所はデフォのままでいい）
    - name
        - デバイスの名前
            - デバイス名の取得はおまけ2の結果から、nameの値を持ってくる
        - Request body
            - JSONでtypeとnewPasswordをこんな感じで指定する

```JSON:device-issuecommand-changepassword-sample
{
  "type": "RESET_PASSWORD",
  "newPassword": "1234"
}
```

- 「EXECUTE」をクリックする
    - 前回同様
- 結果が返ってくる

# おまけ4. デバイスの削除

デバイス側でデバイスの初期化を行っても、API上にはデバイス情報が残る。
逆に、API上からデバイスを削除してやればデバイスに対してワイプコマンドが走り、端末が通信できる状態であれば初期化が掛かる。

- [Android Management API - devices - delete](https://developers.google.com/android/management/reference/rest/v1/enterprises.devices/delete?hl=ja)にアクセスする。
- 右に表示される「Try this method」の各項目を入力する（明示していない箇所はデフォのままでいい）
    - name
        - デバイスの名前
            - デバイス名の取得はおまけ2の結果から、nameの値を持ってくる
        - wipeDataFlags
            - PRESERVE_RESET_PROTECTION_DATA
                - デバイスプロテクションの取り扱い
                    - 強制初期化した際に、利用していたGoogleアカウントが求められるアレ
                    - 試しちゃいないが、これを指定することで、保護を存続させるように読み取れる
            - WIPE_EXTERNAL_STORAGE
                - SDカードとかも一緒に消すかのフラグ
- 「EXECUTE」をクリックする
    - 前回同様
- 結果が返ってくる

ただ、この方法だとデバイスに電源が入っていなかったり、通信できない状態であってもAPI上のデバイスは削除され、デバイスが本当にワイプコマンドを受け取ったかどうか判別できなさそうな気がする。
別途、ワイプのみを行う方法がありそう。それを以てコマンド受領を確認した後にAPIからデバイス削除するのがお作法的に良さそうなんだけれども。

# 駄文

このAndroid Management APIや、Test DPCで（たぶん）用いられている[Device Policy Manager API](https://developer.android.com/reference/android/app/admin/DevicePolicyManager)を組み合わせて、WebUIとかを作りこんだのが一般に提供されているEMMである。が、逆にそれらのEMMがこれらのAPIだけを利用している訳でもなさそうなので奥が深くて辛い。

既に新規登録が打ち切られているAndroid Management Experienceだが、過去に登録済みであれば未だに使えなくもない気がしないでもない。
比較した場合、設定できる項目はAndroid Management APIの方が多い。ただしJSONをゴリゴリ書かないといけない。
Android Management Experienceは手軽に設定できるが、設定できる項目が少なすぎてやっぱりお試し程度にしかならない。