自分のアプリからのapkファイルをインストールしたいときがあります。アンドロイドのPackageInstallerServiceはデバイスにapkをインストールできるAPIを提供します。 アプリは、このAPIを使用すると、apkをインストールすることができます。しかし、このAPIを使用するには、android.permission.INSTALL_PACKAGESというパーミッションが必要です。 このパーミッションは、システム(privileged)アプリまたはプラットフォームkeyで署名されたエプマン受けることができますので、一般的なアプリは、この方法を使用することができません。
それでは、どのようアプリをインストールする必要がでしょうか?システムには、PackageInstallerというアプリがあります。このアプリは、上記のパーミッションを持っており、PackageInstallerServiceのAPIを使用してapkをインストールすることができます。私たちは、このアプリにどのapkファイルを代わりにインストールくれ求めることができます。いくつかの条件が、満足されると、PackageInstallerアプリは、私たちのapkを代わりに設置します。
アプリがPackageInstallerアプリのインストールを要求する方法について説明します。
この記事で使われたコードは、すべての鼻間違って作成しました。
PackageInstallerアプリは何ですか?
このアプリのパッケージ名は com.google.android.packageinstaller
です。このアプリは、Googleが提供するappです。すべてのデバイスには、このアプリがインストールされています。
私たちは、このアプリにインテントを送信インストールを要求することができます。
Apk準備する
アプリのインストールを要求する前に、apkファイルが必要です。皆さんのアプリは、サーバから取得したapk、または事前に準備されたapkをインストールするシナリオを持っていることがあります。 私はAssetsにapkを保存しておき、これをアプリのfilesフォルダにコピーした後PackageInstallerにインストールを要求するように実装しました。
次のコードは、 /assets/app.apk
ファイルをアプリのfilesフォルダである「/data/data/comd.codechacha.sample/files/app.apk」にコピーするコードです。
val inputStream = assets.open("app.apk")
val outPath= filesDir.absolutePath + "/app.apk"
val outputStream = FileOutputStream(outPath)
while (true) {
val data = inputStream.read()
if (data == -1) {
break
}
outputStream.write(data)
}
inputStream.close()
outputStream.close()
adb shell
で目的の場所にファイルがコピーされたことを確認しました。
generic_x86:/data/data/com.codechacha.sample/files # ls
app.apk
Apkインストールを求める
今までのアプリのデータフォルダーにapkを受けた状態です。 これで、このファイルをPackageInstallerにインストールを要請します。
まず、アプリのAndroidManifest.xmlに以下のようにパーミッションを登録する必要があります。 アプリがインストールされるように、この権限を受けません。この権限は、ユーザーが設定アプリから直接付与する必要が得ることができます。 権限を取得する方法は、以下に説明します。
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
次のコードは、アプリのapkをPackageInstallerにインストールを要請するコードです。
val apkPath= filesDir.absolutePath + "/app.apk"
val apkUri =
FileProvider.getUriForFile(applicationContext,
BuildConfig.APPLICATION_ID + ".fileprovider", File(apkPath)) // 1
val intent = Intent(Intent.ACTION_VIEW) // 2
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 3
intent.setDataAndType(apkUri, "application/vnd.android.package-archive") // 4
startActivity(intent) // 6
"// 1"
のように番号で表記したものを以下に詳細に説明しました。
- UriはApkファイルの位置情報を持っています。ここFileProviderを使用したがね。
Androidは、異なるアプリのデータフォルダーに直接アクセスすることを禁止するからです。
FileProviderを使用すると、アプリ同士でデータを共有することができます。
2. PackageInstallerを実行するためには、暗黙的インテントのactionはACTION_VIEWに設定します。
3.インテントを受信したPackageInstallerが自分のアプリのデータにアクセスするための権限を付与します。
4. Dataは上記で作成しUriを入れて、インテントのタイプは、"application/vnd.android.package-archive"
に設定します。
タイプはPackageInstallerを暗黙的インテントに見つける必要があります。
上記のコードでapkファイルをPackageInstallerに提供する際にFileProviderを利用しました。
FileProviderで、他のアプリとファイルを共有するには、AndroidManifest.xmlに以下のようにproviderが定義されます。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
@xml/file_paths
はres/xml/file_paths.xml
ファイルを指し、次のように定義すると、されます。
このように設定すると、私のアプリのデータフォルダーの ../files/app.apk
ファイルを他のアプリと共有することができます。
<paths>
<files-path path="/" name="app.apk" />
</paths>
上記のコードを実行すると、次の画面が出ます。この画面は、セッティングに行ってアプリにインストールを要求することができる権限を与えなければならないということです。
セキュリティ上の理由から、アプリがapkのインストールを要求するユーザーに許可を取らなければします。一度だけ設定すると、その次からは出ません。
Settingsボタンを押すと、次の画面が出ます。 Allow from this source
を押して有効にすると、このアプリは、apkをインストール要求する権限を与えられます。
再インストール画面に戻ると、次のような画面が表示されます。今 Install
ボタンを押すと、インストールがされます。
パーミッションの設定画面オフセット
アプリのインストールを要求したとき、上から見たように、権限がない場合の設定画面に移動するように案内メッセージが表示されます。 しかし、アプリでプレビュー表示し権限を受けたい場合があります。
次のコードは、権限を持っているかどうかチェックしていない場合はセッティング画面を浮かべるコードです。
if (packageManager.canRequestPackageInstalls()) { // 1
// request to install apk
} else {
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, // 2
Uri.parse("package:$packageName"))
startActivityForResult(intent, REQUEST_INSTALL_PERMISSION) // 3
}
1.権限がある場合はcanRequestPackageInstalls()はtrueを返します。 2.権限を付与するセッティング画面のインテントです。 3.実行すると、設定画面が表示されます。
セッティング画面が終了したら、onActivityResult()が呼び出され、アプリをインストールしたり、他の処理を行うことができます。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_INSTALL_PERMISSION) {
//....
}
}
アプリの削除依頼する
PackageManagerにどのアプリを削除を要請すると、ポップアップが開いたまま、ユーザーが許可をするアプリは削除されます。
アプリが削除を要求する前に、次のようなパーミッションを持っている必要があります。 インストールの権限とは異なり、削除の権限は、AndroidManifest.xmlに登録するだけで、アプリがインストールされたときの権限を受け取ります。
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
そして、次のようなコードでどのパッケージに削除を要求します。
val packageURI = Uri.parse("package:com.komorebi.memo") // 1
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI) // 2
startActivity(uninstallIntent) // 3
- "package:[削除したいパッケージ名]"の形式でUriを作成します。
- インテントのActionは「ACTION_DELETE」を使用します。
- インテントを実行すると、削除済みの画面が表示されます。
上記のコードが実行されると、次のような画面が開いて、ユーザーが[OK]ボタンを押すと、アプリが削除されます。
まとめ
アプリでapkをインストールしたり、削除する方法について説明しました。 プラットフォームAPIを直接使用することができないため、PackageInstallerというアプリを介して削除とインストールを行うことができます。 PackageInstallerを使用してインストールする理由は、ユーザーこっそり悪意のあるアプリがインストールされたり、アプリが削除されることを防止するためです。 したがって、いくつかのアプリをインストールしたい場合は、ユーザーにこのアプリがインストールされた理由を十分に納得できるようにUXを設定する必要があります。
この記事で作成したサンプルアプリは、GitHubで確認することができます。
参考
Related Posts
- Android - 振動、Vibrator、VibrationEffectの例
- Android - TabLayoutの実装方法(+ ViewPager2)
- Android - PackageManagerにPackage情報を取得する
- Android - ACTION_BOOT_COMPLETEDイベント受信
- Android - FusedLocationProviderClientに位置情報を取得する
- Android - GPS、Network位置情報を取得する(LocationManager)
- Android - Foreground Service実行
- Android - 時間、日付、変更イベント受信
- Android - currentTimeMillis()、elapsedRealtime()、uptimeMillis()
- Android-PowerManager WakeLock
- Android - ファイル入出力の例(Read、Write、内部、外部ストレージ)
- Android - Screen On / Offイベントの受信、状態確認
- Android - 他のアプリのServiceにバインド
- Android - Handler vs Executor
- Android - Darkmode有効にする方法
- Android - hasSystemFeature()、サポートされているFeature確認
- Android - アプリの権限を確認(Permission check)
- Android - インストールされてアプリリストをインポートする
- Android App Shortcuts実装
- Android - ContentProviderを実装、および例
- Android - AIDLを利用して、Remote Serviceの実装
- Android - Uri、Scheme、SSP(Scheme Specific Part)説明
- Android - アプリのインストール、削除、イベントダウンロード(BroadcastReceiverインテントを受け取る)
- Android - SharedPreferencesに簡単なデータを保存する方法
- Android - AlarmManagerにアラームを登録する方法、および例
- Android - Quick SettingsにCustom Tile追加する方法(kotlin)
- Android - Broadcast Receiver登録およびイベントの受信方法
- Android - Runtime permissionリクエスト方法と例(kotlin)
- Android - ネットワーク(WIFI)の接続状態を確認し、変更の検出
- Mockito - static、final methodをmockingする方法
- Andriod - カスタムパーミッションを定義する方法
- RobolectricにUnit Testを作成する(kotlin)
- Android Mockitoのテストコードを作成する(kotlin)
- Android - Handlerの使用方法、および例
- Android - IntentService使用方法
- Android - JobIntentService使用方法