221 lines
8.8 KiB
Java
221 lines
8.8 KiB
Java
![]() |
/*
|
||
|
* 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 java.util.Objects;
|
||
|
|
||
|
/**
|
||
|
* 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) {
|
||
|
Objects.requireNonNull(callback);
|
||
|
Objects.requireNonNull(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) {
|
||
|
Objects.requireNonNull(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) {
|
||
|
}
|
||
|
}
|
||
|
}
|