はじめに
Androidアプリ開発において、システムの電源メニュー(再起動、電源オフなどのダイアログ)をプログラムから表示したいというニーズがあります。通常、この電源メニューは電源ボタンの長押しで表示されますが、アクセシビリティサービスを利用することで、アプリからこのメニューを呼び出すことが可能です。
本記事では、AccessibilityServiceのGLOBAL_ACTION_POWER_DIALOGを使用して、システムの電源メニューを表示する方法を詳しく解説します。
想定される用途
- クイック設定タイルからの電源メニュー表示
- ジェスチャー操作による電源メニュー起動
- 物理ボタンが故障した端末での代替操作
- カスタムランチャーでの電源管理機能
この記事で学べること
- アクセシビリティサービスの基本的な実装方法
- グローバルアクションを使ったシステム機能の呼び出し
- ユーザーガイダンスの実装方法
開発環境
本記事で使用した開発環境は以下の通りです:
| 項目 | バージョン/内容 |
|---|---|
| IDE | Android Studio Ladybug | 2024.2.1 以降 |
| 言語 | Kotlin 1.9.20 以降 |
| Gradle | 8.x |
| compileSdk | API 36 (Android 15) |
| minSdk | API 26 (Android 8.0) |
| targetSdk | API 36 (Android 15) |
主な依存関係
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
}
必要な知識
- Kotlinの基本文法
- Androidの基本的なコンポーネント(Activity、Service)
- Intentの使い方
実装説明
アーキテクチャ概要
システムの電源メニューを表示するには、以下の3つのコンポーネントが必要です:
┌─────────────────┐
│ MainActivity │ ← ユーザーインターフェース
└────────┬────────┘
│
↓
┌─────────────────────────┐
│ AccessibilityHelper │ ← ヘルパークラス(状態チェック)
└────────┬────────────────┘
│
↓
┌──────────────────────────────┐
│ PowerAccessibilityService │ ← アクセシビリティサービス
└──────────────────────────────┘
│
↓
┌──────────────────────────────┐
│ GLOBAL_ACTION_POWER_DIALOG │ ← システムAPI
└──────────────────────────────┘
処理フロー
- ユーザーアクション: ユーザーがボタンをタップ
- サービスチェック: アクセシビリティサービスが有効か確認
- Intent送信: 有効な場合、Serviceに処理を依頼
- グローバルアクション実行:
performGlobalAction(GLOBAL_ACTION_POWER_DIALOG)を呼び出し - 結果: システムの電源メニューが表示される
重要なポイント
なぜアクセシビリティサービスが必要か?
通常のアプリには、システムレベルの機能(電源メニューなど)にアクセスする権限がありません。しかし、AccessibilityServiceは障害を持つユーザーを支援するための特権を持っており、その一環としてperformGlobalAction()を使用できます。
セキュリティ上の考慮事項
- アクセシビリティサービスは強力な権限を持つため、ユーザーが手動で有効化する必要がある
- Google Playでの公開時は、なぜこの権限が必要かを明確に説明する必要がある
- 悪用を防ぐため、最小限の機能のみを実装することが推奨される
実装
それでは、実際のコードを見ていきましょう。
1. アクセシビリティサービスの実装
まず、アクセシビリティサービスを継承したクラスを作成します。
PowerAccessibilityService.kt
package jp.upft.systempowerdialogapp
import android.accessibilityservice.AccessibilityService
import android.content.Intent
import android.util.Log
import android.view.accessibility.AccessibilityEvent
class PowerAccessibilityService : AccessibilityService() {
companion object {
private const val TAG = "PowerAccessService"
const val ACTION_POWER_DIALOG = "jp.upft.systempowerdialogapp.action.POWER_DIALOG"
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
// このアプリでは特にイベント処理は不要
}
override fun onInterrupt() {
Log.d(TAG, "Service interrupted")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand called")
intent?.action?.let { action ->
Log.d(TAG, "Action received: $action")
if (action == ACTION_POWER_DIALOG) {
Log.d(TAG, "Performing GLOBAL_ACTION_POWER_DIALOG")
val success = performGlobalAction(GLOBAL_ACTION_POWER_DIALOG)
Log.d(TAG, "Power dialog result: $success")
}
}
return super.onStartCommand(intent, flags, startId)
}
}
ポイント:
onStartCommand(): Intentを受け取り、カスタムアクションを処理performGlobalAction(GLOBAL_ACTION_POWER_DIALOG): システムの電源メニューを表示- ログ出力: デバッグやトラブルシューティングに役立つ
2. ヘルパークラスの実装
サービスの状態チェックと電源メニュー表示のロジックをまとめたヘルパークラスを作成します。
AccessibilityHelper.kt
package jp.upft.systempowerdialogapp
import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.view.accessibility.AccessibilityManager
import android.widget.Toast
object AccessibilityHelper {
/**
* アクセシビリティサービスが有効かチェック
*/
fun isServiceEnabled(context: Context): Boolean {
val manager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
?: return false
val serviceInfoList = manager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK
)
val packageName = context.packageName
val serviceName = PowerAccessibilityService::class.java.name
return serviceInfoList.any { serviceInfo ->
val info = serviceInfo.resolveInfo.serviceInfo
info.packageName == packageName && info.name == serviceName
}
}
/**
* パワーダイアログを表示
*/
fun showPowerDialog(context: Context): Boolean {
if (!isServiceEnabled(context)) {
showServiceDisabledMessage(context)
return false
}
return try {
val intent = Intent(context, PowerAccessibilityService::class.java).apply {
action = PowerAccessibilityService.ACTION_POWER_DIALOG
}
context.startService(intent)
true
} catch (e: Exception) {
Toast.makeText(
context,
"エラー: ${e.message}",
Toast.LENGTH_SHORT
).show()
false
}
}
/**
* サービス無効メッセージを表示して設定画面へ誘導
*/
private fun showServiceDisabledMessage(context: Context) {
Toast.makeText(
context,
"アクセシビリティサービスを有効にしてください",
Toast.LENGTH_LONG
).show()
openAccessibilitySettings(context)
}
/**
* アクセシビリティ設定画面を開く
*/
fun openAccessibilitySettings(context: Context) {
try {
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(
context,
"設定画面を開けませんでした",
Toast.LENGTH_SHORT
).show()
}
}
}
ポイント:
isServiceEnabled(): サービスの有効性を確認showPowerDialog(): 電源メニュー表示のメインロジック- エラーハンドリング: try-catchでエラーを適切に処理
- ユーザーガイダンス: サービスが無効な場合は設定画面へ誘導
3. メインアクティビティの実装
ユーザーインターフェースを持つメインアクティビティを実装します。
MainActivity.kt
package jp.upft.systempowerdialogapp
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// パワーダイアログを表示するボタン
findViewById<Button>(R.id.btnShowPowerDialog).setOnClickListener {
AccessibilityHelper.showPowerDialog(this)
}
// サービス状態をチェックするボタン
findViewById<Button>(R.id.btnCheckService).setOnClickListener {
val isEnabled = AccessibilityHelper.isServiceEnabled(this)
val message = if (isEnabled) {
"アクセシビリティサービスは有効です"
} else {
"アクセシビリティサービスは無効です"
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// 設定画面を開くボタン
findViewById<Button>(R.id.btnOpenSettings).setOnClickListener {
AccessibilityHelper.openAccessibilitySettings(this)
}
}
override fun onResume() {
super.onResume()
// 画面に戻ったときにサービス状態を確認
checkServiceStatus()
}
private fun checkServiceStatus() {
if (!AccessibilityHelper.isServiceEnabled(this)) {
Toast.makeText(
this,
"アクセシビリティサービスが無効です",
Toast.LENGTH_SHORT
).show()
}
}
}
ポイント:
- 3つのボタン: 電源メニュー表示、状態確認、設定画面起動
onResume(): アプリに戻った際にサービス状態を再チェック- シンプルなUI: ユーザーにとって分かりやすいインターフェース
4. レイアウトファイル
シンプルで使いやすいUIを作成します。
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_title"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="32dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_description"
android:textSize="14sp"
android:gravity="center"
android:layout_marginBottom="32dp" />
<Button
android:id="@+id/btnShowPowerDialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/btn_show_power_dialog"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btnCheckService"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/btn_check_service"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btnOpenSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/btn_open_settings" />
</LinearLayout>
5. アクセシビリティサービス設定
サービスの動作を定義するXMLファイルを作成します。
res/xml/accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:description="@string/accessibility_service_description"
android:summary="@string/accessibility_service_summary" />
6. 文字列リソース
res/values/strings.xml
<resources>
<string name="app_name">SystemPowerDialogApp</string>
<!-- UI -->
<string name="app_title">システムパワーダイアログ</string>
<string name="app_description">電源メニューを表示するアプリです。\n使用するにはアクセシビリティサービスを有効にしてください。</string>
<string name="btn_show_power_dialog">電源メニューを表示</string>
<string name="btn_check_service">サービス状態を確認</string>
<string name="btn_open_settings">アクセシビリティ設定を開く</string>
<!-- アクセシビリティサービス関連 -->
<string name="accessibility_service_label">パワーダイアログサービス</string>
<string name="accessibility_service_summary">電源メニューを表示するためのサービス</string>
<string name="accessibility_service_description">このアプリがシステムの電源メニューを表示できるようにします。\n個人情報の収集や他のアプリの操作は行いません。</string>
</resources>
ポイント:
description: ユーザーがサービスを有効にする際に表示される説明文- セキュリティとプライバシーへの配慮を明記
7. AndroidManifest.xml
サービスをシステムに登録します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SystemPowerDialogApp">
<!-- メインアクティビティ -->
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- アクセシビリティサービス(重要) -->
<service
android:name=".PowerAccessibilityService"
android:label="@string/accessibility_service_label"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
</application>
</manifest>
重要な設定:
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE": 必須のパーミッションandroid:exported="false": セキュリティのため、外部からの起動を禁止<intent-filter>: アクセシビリティサービスとして登録<meta-data>: サービス設定ファイルを参照
結果
実行手順
- アプリを起動
- アクセシビリティ設定を開く
- 「パワーダイアログサービス」をタップ
- トグルスイッチをONにする
- 警告ダイアログで「許可」をタップ
- トップに戻る
- 電源メニューを表示
- 2. 終了後、「電源メニューを表示」をタップ
- システムの電源メニューが表示される
実行動画
ログ出力
Logcatで以下のようなログが確認できます:
D/PowerAccessService: onStartCommand called
D/PowerAccessService: Action received: jp.upft.systempowerdialogapp.action.POWER_DIALOG
D/PowerAccessService: Performing GLOBAL_ACTION_POWER_DIALOG
D/PowerAccessService: Power dialog result: true
トラブルシューティング
電源メニューが表示されない場合
原因1: アクセシビリティサービスが有効になっていない
- 確認箇所: 「サービス状態を確認」ボタンで状態をチェックし、必要に応じて設定画面から有効化
原因2: サービスの登録が正しくない
- 確認箇所: AndroidManifest.xmlの設定を再確認
原因3: 端末の制限
- 確認箇所: 一部の端末メーカーはカスタムROMでこの機能を制限している場合がある
まとめ
本記事では、Androidのアクセシビリティサービスを利用して、システムの電源メニューをプログラムから表示する方法を解説しました。
学んだこと
- ✅ アクセシビリティサービスの基本的な実装方法
- ✅
GLOBAL_ACTION_POWER_DIALOGの使い方 - ✅ サービスの状態チェックとユーザーガイダンスの重要性
- ✅ AndroidManifest.xmlでのサービス登録方法
応用例
この技術を応用すると、以下のような機能も実装できます:
- クイック設定タイル: 通知パネルから電源メニューを起動
- ウィジェット: ホーム画面ウィジェットからの起動
- ジェスチャー: スワイプやタップジェスチャーでの起動
- 他のグローバルアクション:
GLOBAL_ACTION_NOTIFICATIONS: 通知パネルを開くGLOBAL_ACTION_RECENTS: 最近のアプリを表示GLOBAL_ACTION_HOME: ホーム画面に戻る
セキュリティとプライバシーへの配慮
アクセシビリティサービスは強力な権限を持つため、以下の点に注意が必要です:
- 透明性: ユーザーに何をするサービスか明確に説明する
- 最小権限の原則: 必要最小限の機能のみを実装する
- データ保護: 個人情報を収集しない
- Google Playポリシー: 審査基準を満たす説明とプライバシーポリシーを提供
Google Playでの公開について
アクセシビリティサービスを使用するアプリをGoogle Playで公開する場合は、以下の準備が必要です:
- アプリの主要機能としてアクセシビリティ機能を実装する
- なぜアクセシビリティサービスが必要かを明確に説明する
- 適切なプライバシーポリシーを提供する
- スクリーンショットや動画で使用方法を示す
参考リンク
おわりに
この実装により、ユーザーにとってより便利なアプリ体験を提供できます。ただし、強力な権限を持つ機能であるため、責任を持って適切に使用することが重要です。
アクセシビリティサービスは本来、障害を持つユーザーを支援するための機能です。この技術を使用する際は、その本来の目的を尊重し、ユーザーのプライバシーとセキュリティを最優先に考えて開発を進めましょう。








