追記
ツールアプリのE2Eテストとして下記ツールが用意されています。
AppFunctions Testing Agent Pre-Release
これを使えば、AppFunctionsをアプリに組み込んだ(ツール側の)アプリの動作確認ができます。
これはadb shell am instrumentコマンド経由で起動するため、権限周りの下記セットアップが不要になっています。
今回はルート化/特権付与の方法で、エージェントアプリの実装を試すためのセットアップとなります。
はじめに
今年のGoogleI/Oでかなり全面に押し出されていた AppFunctions で色々試していますが、セットアップで気をつけたいところがあるので共有します。
AppFunctions は、自分のアプリの機能を Android システム経由で他のエージェントアプリから呼び出せるようにする仕組みです。
AppFunctionsには Tool/Agent という2つの役割があります。Toolはエージェントへの関数を提供する側で、単純に動かすだけなら特に困ることは有りません。
以下に公式のサンプルもあります。
appfunctions/ChatApp at main · android/appfunctions
このサンプルでは、Toolとしてのチャットアプリの実装あり、以下のようにadbコマンド経由でツールを呼び出すだけで、エージェント側の実装が有りません。
adb shell "cmd app_function execute-app-function \
--package com.example.chatapp \
--function 'com.example.chatapp.appfunctions.AppFunctions#send' \
--parameters '{\"name\": \"Alice\", \"endpointValue\": \"1\", \"messageBody\": \"Hello Alice!\"}'"
エージェント側を実装したとしても、セットアップは権限関連もあり少し工夫が必要ですので、まとめます。
環境、前提
- Android 16 emulator
androidx.appfunctions:1.0.0-alpha09
filipfan/appfunctionspilot
上に先駆者さんのサンプルがあり、参考に手順を進めさせていただきました。
以下手順はエージェントアプリを使用するためのもので、ツールアプリだけ動かす場合は不要です。
手順①: ルート化する
Android StudioからDevice Managerでroot化可能なエミュレータ(Android 16)を作成します。
このとき Google APIs を選択してください(Google Playの方もありますが、これはroot化できません)。
エミュレータはcliから-writable-system付きで起動する必要があります。
(Android Studio からの起動ではこのオプションが付かないため使えません)
これは/system パーティションを読み書き可能にし、エージェントアプリを 特権アプリとして配置するために必要です。
特権アプリはプリインストールされたアプリによくあるもので、通常のアプリよりも強い権限で動作します。
~/Library/Android/sdk/emulator/emulator -avd <avd-name> -writable-system
その後次の手順でroot化します。
adb root
手順②: schema parser フラグを確認する
以下コマンドで、フラグがenabledであることを確認します。
adb shell aflags list | grep "enable_app_functions_schema_parser"
これが無効だとAgent 側で AppFunctionManager.observeAppFunctions() を呼んでも、システムが metadata を返してくれず、空の結果になります。
上記コマンドはroot化されていないとError: must be rootとなり、確認できません。
enabled でなければ無効です。
とはいえ、自分のsimulatorではenabledでした。
無効であればenabledにして(adb shell aflags enable com.android.appfunctions.flags.enable_app_functions_schema_parser)、再起動し、toolアプリ、agentアプリをインストールします。
adb reboot
./gradlew :tool:installDebug :app:installDebug
(モジュール名は適宜読み替える)
しかしまだappFunctionManager.observeAppFunctions() によって、ツールアプリのmetadata を取ることはできません。
以下AppFunctionManagerのkDocです。
When holding the android.permission.EXECUTE_APP_FUNCTIONS permission - functions in other packages that it is allowed to query via android.content.pm.PackageManager.canPackageQuery.
手順③: EXECUTE_APP_FUNCTIONS の権限を付与する
Agent アプリは android.permission.EXECUTE_APP_FUNCTIONS という権限が必要です。
privileged な権限は、system 領域に置かれたアプリにしか付与されません。そのため普通に adb install しても権限が降りず、Agent が他アプリの関数を呼べない状態になります。
まず確認してみます。
adb shell pm list permissions -f | grep -A 5 "EXECUTE_APP_FUNCTIONS"
出力
+ permission:android.permission.EXECUTE_APP_FUNCTIONS
package:android
label:null
description:null
protectionLevel:internal|privileged
+ permission:com.google.android.configupdater.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION
protectionLevel に privileged が含まれています。
特権アプリにのみ付与される権限であることがわかります。
以下の手順で system アプリとして配置します。
remountはsystemパーティションを読み書き可能にします。
systemアプリとして認識させるためrebootも必要です。
# rooted emulator で root + remount
adb root
adb remount
# privileged permission の enforcement を log モードに変更
adb shell "sed -i 's/ro.control_privapp_permissions=enforce/ro.control_privapp_permissions=log/g' /vendor/build.prop"
# privileged アプリとして配置 apkは適宜読み替える
adb push app/build/outputs/apk/debug/app-debug.apk /system/priv-app/
# 反映のため再起動
adb reboot
これによりAppFunctionManager.observeAppFunctions() で metadata を取れるようになり、かつツールを呼び出せるようになります。
さいごに
もし上記誤りがあればコメント等でご指摘いただけると助かります。
さて、ここからは推測が入ります。
このようなエージェント側の敷居の高さは、セキュリティなどを考慮したものと考えられます。
誰でもエージェントアプリを作れてしまうと、他のアプリの機能を悪用される可能性があります。
したがって現在特権アプリしか許されない厳しい制約があります。
アプリ開発者がどれだけエージェント側のアプリを実装する機会があるかも未知数です。
とはいえ開発者はroot化Simulator等でエージェント側アプリを試してみることはできます。
エージェント側アプリの実装を知ることで、しくみへの理解が深まり、ツール側の実装にも役立つこともあるかもしれません。
ただ、今後どうなるかはわかりません。
AppFunctions はまだ alpha なので、バージョンによってここで書いた挙動も変わりうると思います。
引き続きAppFunctionsの動向を追っていきたいと思います。