File: AccessibilityButtonController.java

package info (click to toggle)
android-platform-frameworks-base 1%3A10.0.0%2Br36-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 321,788 kB
  • sloc: java: 962,234; cpp: 274,314; xml: 242,770; python: 5,060; sh: 1,432; ansic: 494; makefile: 47; sed: 19
file content (220 lines) | stat: -rw-r--r-- 9,070 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.accessibilityservice;

import android.annotation.NonNull;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.util.Preconditions;

/**
 * Controller for the accessibility button within the system's navigation area
 * <p>
 * This class may be used to query the accessibility button's state and register
 * callbacks for interactions with and state changes to the accessibility button when
 * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
 * </p>
 * <p>
 * <strong>Note:</strong> This class and
 * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} should not be used as
 * the sole means for offering functionality to users via an {@link AccessibilityService}.
 * Some device implementations may choose not to provide a software-rendered system
 * navigation area, making this affordance permanently unavailable.
 * </p>
 * <p>
 * <strong>Note:</strong> On device implementations where the accessibility button is
 * supported, it may not be available at all times, such as when a foreground application uses
 * {@link android.view.View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}. A user may also choose to assign
 * this button to another accessibility service or feature. In each of these cases, a
 * registered {@link AccessibilityButtonCallback}'s
 * {@link AccessibilityButtonCallback#onAvailabilityChanged(AccessibilityButtonController, boolean)}
 * method will be invoked to provide notifications of changes in the accessibility button's
 * availability to the registering service.
 * </p>
 */
public final class AccessibilityButtonController {
    private static final String LOG_TAG = "A11yButtonController";

    private final IAccessibilityServiceConnection mServiceConnection;
    private final Object mLock;
    private ArrayMap<AccessibilityButtonCallback, Handler> mCallbacks;

    AccessibilityButtonController(@NonNull IAccessibilityServiceConnection serviceConnection) {
        mServiceConnection = serviceConnection;
        mLock = new Object();
    }

    /**
     * Retrieves whether the accessibility button in the system's navigation area is
     * available to the calling service.
     * <p>
     * <strong>Note:</strong> If the service is not yet connected (e.g.
     * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
     * service has been disconnected, this method will have no effect and return {@code false}.
     * </p>
     *
     * @return {@code true} if the accessibility button in the system's navigation area is
     * available to the calling service, {@code false} otherwise
     */
    public boolean isAccessibilityButtonAvailable() {
        if (mServiceConnection != null) {
            try {
                return mServiceConnection.isAccessibilityButtonAvailable();
            } catch (RemoteException re) {
                Slog.w(LOG_TAG, "Failed to get accessibility button availability.", re);
                re.rethrowFromSystemServer();
                return false;
            }
        }
        return false;
    }

    /**
     * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
     * changes callbacks related to the accessibility button.
     *
     * @param callback the callback to add, must be non-null
     */
    public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback) {
        registerAccessibilityButtonCallback(callback, new Handler(Looper.getMainLooper()));
    }

    /**
     * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
     * change callbacks related to the accessibility button. The callback will occur on the
     * specified {@link Handler}'s thread, or on the services's main thread if the handler is
     * {@code null}.
     *
     * @param callback the callback to add, must be non-null
     * @param handler the handler on which the callback should execute, must be non-null
     */
    public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback,
            @NonNull Handler handler) {
        Preconditions.checkNotNull(callback);
        Preconditions.checkNotNull(handler);
        synchronized (mLock) {
            if (mCallbacks == null) {
                mCallbacks = new ArrayMap<>();
            }

            mCallbacks.put(callback, handler);
        }
    }

    /**
     * Unregisters the provided {@link AccessibilityButtonCallback} for interaction and state
     * change callbacks related to the accessibility button.
     *
     * @param callback the callback to remove, must be non-null
     */
    public void unregisterAccessibilityButtonCallback(
            @NonNull AccessibilityButtonCallback callback) {
        Preconditions.checkNotNull(callback);
        synchronized (mLock) {
            if (mCallbacks == null) {
                return;
            }

            final int keyIndex = mCallbacks.indexOfKey(callback);
            final boolean hasKey = keyIndex >= 0;
            if (hasKey) {
                mCallbacks.removeAt(keyIndex);
            }
        }
    }

    /**
     * Dispatches the accessibility button click to any registered callbacks. This should
     * be called on the service's main thread.
     */
    void dispatchAccessibilityButtonClicked() {
        final ArrayMap<AccessibilityButtonCallback, Handler> entries;
        synchronized (mLock) {
            if (mCallbacks == null || mCallbacks.isEmpty()) {
                Slog.w(LOG_TAG, "Received accessibility button click with no callbacks!");
                return;
            }

            // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
            // modification.
            entries = new ArrayMap<>(mCallbacks);
        }

        for (int i = 0, count = entries.size(); i < count; i++) {
            final AccessibilityButtonCallback callback = entries.keyAt(i);
            final Handler handler = entries.valueAt(i);
            handler.post(() -> callback.onClicked(this));
        }
    }

    /**
     * Dispatches the accessibility button availability changes to any registered callbacks.
     * This should be called on the service's main thread.
     */
    void dispatchAccessibilityButtonAvailabilityChanged(boolean available) {
        final ArrayMap<AccessibilityButtonCallback, Handler> entries;
        synchronized (mLock) {
            if (mCallbacks == null || mCallbacks.isEmpty()) {
                Slog.w(LOG_TAG,
                        "Received accessibility button availability change with no callbacks!");
                return;
            }

            // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
            // modification.
            entries = new ArrayMap<>(mCallbacks);
        }

        for (int i = 0, count = entries.size(); i < count; i++) {
            final AccessibilityButtonCallback callback = entries.keyAt(i);
            final Handler handler = entries.valueAt(i);
            handler.post(() -> callback.onAvailabilityChanged(this, available));
        }
    }

    /**
     * Callback for interaction with and changes to state of the accessibility button
     * within the system's navigation area.
     */
    public static abstract class AccessibilityButtonCallback {

        /**
         * Called when the accessibility button in the system's navigation area is clicked.
         *
         * @param controller the controller used to register for this callback
         */
        public void onClicked(AccessibilityButtonController controller) {}

        /**
         * Called when the availability of the accessibility button in the system's
         * navigation area has changed. The accessibility button may become unavailable
         * because the device shopped showing the button, the button was assigned to another
         * service, or for other reasons.
         *
         * @param controller the controller used to register for this callback
         * @param available {@code true} if the accessibility button is available to this
         *                  service, {@code false} otherwise
         */
        public void onAvailabilityChanged(AccessibilityButtonController controller,
                boolean available) {
        }
    }
}