web-dev-qa-db-ja.com

Androidハードウェアのキー押下をリッスンするサービスを作成することは可能ですか?

Androidバックグラウンドサービスを実行したいのですが、ホーム画面から、または電話がスリープ状態のときにキーリスナーとして機能します。これは可能ですか?

オンラインの準関連の例から、次のサービスをまとめましたが、「サービスタイプのonKeyDownが定義されていません」というエラーが表示されます。これは、Launcherを書き直さないと実行できないことを意味しますか、それとも私が見逃している明らかなものがありますか?

public class ServiceName extends Service {

    @Override
    public void onCreate() {
        //Stuff
    }

    public IBinder onBind(Intent intent) {
        //Stuff
        return null;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(event.getAction() == KeyEvent.ACTION_DOWN) {
        switch(keyCode) {
        case KeyEvent.KEYCODE_A:
            //Stuff
            return true;
        case KeyEvent.KEYCODE_B:
            //Stuff
            return true;

            //etc.
        }
        }

        return super.onKeyDown(keyCode, event);
    }
}

Androidホーム画面から入力すると、デフォルトで検索バーが表示されますが、これは非常に特別な用途のためのものです。他の人には期待していません。たとえば、カメラボタンを使用して電話を復帰させるのはいいことだと思います。

28
Brian

私が知る限り、KeyEventsは、ユーザーがキーを押すためのインターフェースであるため、アクティビティによってのみ処理できます。サービスはバックグラウンドで実行され、ユーザー入力に反応することは意図されていません。これは、コンパイラの警告「ServiceタイプのonKeyDownが定義されていない」の理由でもあります。サービスまたはそのスーパークラスは、KeyEvent.Callbackインターフェースを実装していません。回避策として、AndroidManifest.xmlなどの特定のシステム通知に反応するようにAndroid.intent.action.SCREEN_ONにアクティビティを登録できます。電源ボタンを押して画面をオンにすると、アクティビティが開始され、何らかのサービスが初期化されてバックグラウンドに戻ります。それがあなたがやろうとしていることなら。可能なアクションについては Intent docs を参照してください。

お役に立てば幸い...

16
MarioB.

これにはLollipop(v5.0/API 21)以降が必要で、ボリュームキーのみを検出できます

ボリュームキーアクションをオーバーライドするため、グローバルに使用することは望ましくない場合があります。

public class VolumeKeyController {

    private MediaSessionCompat mMediaSession;
    private final Context mContext;

    public VolumeKeyController(Context context) {
        mContext = context;
    }

    private void createMediaSession() {
        mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);

        mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
        mMediaSession.setPlaybackState(new Builder()
                .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
                .build());
        mMediaSession.setPlaybackToRemote(getVolumeProvider());
        mMediaSession.setActive(true);
    }

    private VolumeProviderCompat getVolumeProvider() {
        final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);

        int STREAM_TYPE = AudioManager.STREAM_MUSIC;
        int currentVolume = audio.getStreamVolume(STREAM_TYPE);
        int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
        final int VOLUME_UP = 1;
        final int VOLUME_DOWN = -1;

        return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
            @Override
            public void onAdjustVolume(int direction) {
                // Up = 1, Down = -1, Release = 0
                // Replace with your action, if you don't want to adjust system volume
                if (direction == VOLUME_UP) {
                    audio.adjustStreamVolume(STREAM_TYPE,
                            AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                }
                else if (direction == VOLUME_DOWN) {
                    audio.adjustStreamVolume(STREAM_TYPE,
                            AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                }
                setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
            }
        };
    }

    // Call when control needed, add a call to constructor if needed immediately
    public void setActive(boolean active) {
        if (mMediaSession != null) {
            mMediaSession.setActive(active);
            return;
        }
        createMediaSession();
    }

    // Call from Service's onDestroy method
    public void destroy() {
        if (mMediaSession != null) {
            mMediaSession.release();
        }
    }
}
4
Gibolt

KeyEventsでは、アクティビティをトリガーする必要があります。したがって、サービスにはホストアクティビティがないため、ハードウェアキーが押されたことはサービスを介して検出できません。 SystemOverlayを要求して、透過的なアクティビティを作成できます。しかし、このアプローチはAPI 26以上のデバイスでは機能しないようです。

これの回避策は、AccessibilityServicesを介してオブザーバーを設定することです。これにより、ハードウェアキーの押下をグローバルに検出できます。

:アプリをユーザー補助アプリとして有効にすると、重大なセキュリティ問題が発生する可能性があり、ユーザーはこれを有効にすることに注意する必要があります。したがって、これは、アプリケーションが処理するデータに関して、アプリケーションがユーザーに対して「透過的」である可能性がある状況で推奨されます。このメソッドはすべてのAPI 21+デバイスで機能します。これより下のデバイスではテストしていないため、機能する場合と機能しない場合があります。

手順:

  • 次のオプションを使用してXMLファイルを作成します

      <accessibility-service
        Android:accessibilityFlags="flagRequestFilterKeyEvents"
        Android:accessibilityEventTypes="typeAllMask"
        Android:accessibilityFeedbackType="feedbackAllMask"
        Android:notificationTimeout="100"
        Android:canRetrieveWindowContent="true"
        Android:settingsActivity=""
        Android:packageNames="yourpackagename"
        Android:canRequestFilterKeyEvents="true"
      />
    
  • マニフェストでAccessibilityServiceを定義する

    <service Android:name=".Services.AccessibilityKeyDetector"
            Android:permission="Android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action Android:name="Android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data Android:name="Android.accessibilityservice"
                Android:resource="@xml/accessibility_layout" />
    </service>
    
  • AccessibilityServiceクラスを作成する

public class AccessibilityKeyDetector extends AccessibilityService {

    private final String TAG = "AccessKeyDetector";

    @Override
    public boolean onKeyEvent(KeyEvent event) {
        Log.d(TAG,"Key pressed via accessibility is: "+event.getKeyCode());
        //This allows the key pressed to function normally after it has been used by your app.
        return super.onKeyEvent(event);
    }


    @Override
    protected void onServiceConnected() {
        Log.i(TAG,"Service connected");

    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

    }


    @Override
    public void onInterrupt() {

    }
}

  • これに対する許可要求を処理するMainActivityを作成します。
public class MainActivity extends AppCompatActivity {

    private final String TAG = "Test";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkAccessibilityPermission();
    }

    public boolean checkAccessibilityPermission() {
        int accessEnabled=0;
        try {
            accessEnabled = Settings.Secure.getInt(this.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            e.printStackTrace();
        }
        if (accessEnabled==0) {
            /** if not construct intent to request permission */
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            /** request permission via start activity for result */
            startActivity(intent);
            return false;
        } else {
            return true;
        }
    }


    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d(TAG,"Key pressed");

//this prevents the key from performing the base function. Replace with super.onKeyDown to let it perform it's original function, after being consumed by your app.
        return true;

    }
}
0
Irfan S

サービスでハードウェアキーの押下を直接聞くことはできませんが、それらのキーの押下の影響を聞くことができる場合があります。たとえば、 この答え は、メディアボリュームの変化からボリュームキーの押下を推測する方法を説明しています。

0
Tad