1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.input; 18 19 import android.annotation.BinderThread; 20 import android.hardware.input.IStickyModifierStateListener; 21 import android.os.IBinder; 22 import android.os.RemoteException; 23 import android.util.Log; 24 import android.util.Slog; 25 import android.util.SparseArray; 26 27 import com.android.internal.annotations.GuardedBy; 28 29 /** 30 * A thread-safe component of {@link InputManagerService} responsible for managing the sticky 31 * modifier state for A11y Sticky keys feature. 32 */ 33 final class StickyModifierStateController { 34 35 private static final String TAG = "ModifierStateController"; 36 37 // To enable these logs, run: 38 // 'adb shell setprop log.tag.ModifierStateController DEBUG' (requires restart) 39 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 40 41 // List of currently registered sticky modifier state listeners 42 @GuardedBy("mStickyModifierStateListenerRecords") 43 private final SparseArray<StickyModifierStateListenerRecord> 44 mStickyModifierStateListenerRecords = new SparseArray<>(); 45 notifyStickyModifierStateChanged(int modifierState, int lockedModifierState)46 public void notifyStickyModifierStateChanged(int modifierState, int lockedModifierState) { 47 if (DEBUG) { 48 Slog.d(TAG, "Sticky modifier state changed, modifierState = " + modifierState 49 + ", lockedModifierState = " + lockedModifierState); 50 } 51 52 synchronized (mStickyModifierStateListenerRecords) { 53 for (int i = 0; i < mStickyModifierStateListenerRecords.size(); i++) { 54 mStickyModifierStateListenerRecords.valueAt(i).notifyStickyModifierStateChanged( 55 modifierState, lockedModifierState); 56 } 57 } 58 } 59 60 /** Register the sticky modifier state listener for a process. */ 61 @BinderThread registerStickyModifierStateListener(IStickyModifierStateListener listener, int pid)62 public void registerStickyModifierStateListener(IStickyModifierStateListener listener, 63 int pid) { 64 synchronized (mStickyModifierStateListenerRecords) { 65 if (mStickyModifierStateListenerRecords.get(pid) != null) { 66 throw new IllegalStateException("The calling process has already registered " 67 + "a StickyModifierStateListener."); 68 } 69 StickyModifierStateListenerRecord record = new StickyModifierStateListenerRecord(pid, 70 listener); 71 try { 72 listener.asBinder().linkToDeath(record, 0); 73 } catch (RemoteException ex) { 74 throw new RuntimeException(ex); 75 } 76 mStickyModifierStateListenerRecords.put(pid, record); 77 } 78 } 79 80 /** Unregister the sticky modifier state listener for a process. */ 81 @BinderThread unregisterStickyModifierStateListener(IStickyModifierStateListener listener, int pid)82 public void unregisterStickyModifierStateListener(IStickyModifierStateListener listener, 83 int pid) { 84 synchronized (mStickyModifierStateListenerRecords) { 85 StickyModifierStateListenerRecord record = mStickyModifierStateListenerRecords.get(pid); 86 if (record == null) { 87 throw new IllegalStateException("The calling process has no registered " 88 + "StickyModifierStateListener."); 89 } 90 if (record.mListener.asBinder() != listener.asBinder()) { 91 throw new IllegalStateException("The calling process has a different registered " 92 + "StickyModifierStateListener."); 93 } 94 record.mListener.asBinder().unlinkToDeath(record, 0); 95 mStickyModifierStateListenerRecords.remove(pid); 96 } 97 } 98 onStickyModifierStateListenerDied(int pid)99 private void onStickyModifierStateListenerDied(int pid) { 100 synchronized (mStickyModifierStateListenerRecords) { 101 mStickyModifierStateListenerRecords.remove(pid); 102 } 103 } 104 105 // A record of a registered sticky modifier state listener from one process. 106 private class StickyModifierStateListenerRecord implements IBinder.DeathRecipient { 107 public final int mPid; 108 public final IStickyModifierStateListener mListener; 109 StickyModifierStateListenerRecord(int pid, IStickyModifierStateListener listener)110 StickyModifierStateListenerRecord(int pid, IStickyModifierStateListener listener) { 111 mPid = pid; 112 mListener = listener; 113 } 114 115 @Override binderDied()116 public void binderDied() { 117 if (DEBUG) { 118 Slog.d(TAG, "Sticky modifier state listener for pid " + mPid + " died."); 119 } 120 onStickyModifierStateListenerDied(mPid); 121 } 122 notifyStickyModifierStateChanged(int modifierState, int lockedModifierState)123 public void notifyStickyModifierStateChanged(int modifierState, int lockedModifierState) { 124 try { 125 mListener.onStickyModifierStateChanged(modifierState, lockedModifierState); 126 } catch (RemoteException ex) { 127 Slog.w(TAG, "Failed to notify process " + mPid 128 + " that sticky modifier state changed, assuming it died.", ex); 129 binderDied(); 130 } 131 } 132 } 133 } 134