blob: 6aa4702ec7d594f8feb64abea6e220c91a2b2d0e [file] [log] [blame]
/*
* Copyright (C) 2022 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 com.android.server.accessibility;
import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME;
import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Region;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityDisplayProxy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.annotation.Nullable;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Represents the system connection to an {@link AccessibilityDisplayProxy}.
*
* <p>Most methods are no-ops since this connection does not need to capture input or listen to
* hardware-related changes.
*
* TODO(241429275): Initialize this when a proxy is registered.
*/
@SuppressWarnings("MissingPermissionAnnotation")
public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
private int mDeviceId;
private int mDisplayId;
private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
/** The stroke width of the focus rectangle in pixels */
private int mFocusStrokeWidth;
/** The color of the focus rectangle */
private int mFocusColor;
private int mInteractiveTimeout;
private int mNonInteractiveTimeout;
ProxyAccessibilityServiceConnection(
Context context,
ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id,
Handler mainHandler, Object lock,
AccessibilitySecurityPolicy securityPolicy,
SystemSupport systemSupport, AccessibilityTrace trace,
WindowManagerInternal windowManagerInternal,
AccessibilityWindowManager awm, int displayId, int deviceId) {
super(/* userState= */null, context, componentName, accessibilityServiceInfo, id,
mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal,
/* systemActionPerformer= */ null, awm, /* activityTaskManagerService= */ null);
mDisplayId = displayId;
setDisplayTypes(DISPLAY_TYPE_PROXY);
mFocusStrokeWidth = mContext.getResources().getDimensionPixelSize(
R.dimen.accessibility_focus_highlight_stroke_width);
mFocusColor = mContext.getResources().getColor(
R.color.accessibility_focus_highlight_color);
mDeviceId = deviceId;
}
int getDisplayId() {
return mDisplayId;
}
int getDeviceId() {
return mDeviceId;
}
/**
* Called when the proxy is registered.
*/
void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
throws RemoteException {
mServiceInterface = serviceInterface;
mService = serviceInterface.asBinder();
mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
}
/**
* Keeps mAccessibilityServiceInfo in sync with the proxy's list of AccessibilityServiceInfos.
*
* <p>This also sets the properties that are assumed to be populated by installed packages.
*
* @param infos the list of enabled and installed services.
*/
@Override
public void setInstalledAndEnabledServices(List<AccessibilityServiceInfo> infos) {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
mInstalledAndEnabledServices = infos;
final AccessibilityServiceInfo proxyInfo = mAccessibilityServiceInfo;
// Reset values. mAccessibilityServiceInfo is not completely reset since it is final
proxyInfo.flags = 0;
proxyInfo.eventTypes = 0;
proxyInfo.notificationTimeout = 0;
final Set<String> packageNames = new HashSet<>();
boolean hasNullPackagesNames = false;
boolean isAccessibilityTool = false;
int interactiveUiTimeout = 0;
int nonInteractiveUiTimeout = 0;
// Go through and set properties that are relevant to the proxy. This bypasses
// A11yServiceInfo.updateDynamicallyConfigurableProperties since the proxy has
// higher security privileges as a SystemAPI and has to set values at runtime.
for (AccessibilityServiceInfo info : infos) {
isAccessibilityTool = isAccessibilityTool | info.isAccessibilityTool();
if (info.packageNames == null || info.packageNames.length == 0) {
hasNullPackagesNames = true;
} else if (!hasNullPackagesNames) {
packageNames.addAll(Arrays.asList(info.packageNames));
}
interactiveUiTimeout = Math.max(interactiveUiTimeout,
info.getInteractiveUiTimeoutMillis());
nonInteractiveUiTimeout = Math.max(nonInteractiveUiTimeout,
info.getNonInteractiveUiTimeoutMillis());
proxyInfo.notificationTimeout = Math.max(proxyInfo.notificationTimeout,
info.notificationTimeout);
proxyInfo.eventTypes |= info.eventTypes;
proxyInfo.feedbackType |= info.feedbackType;
proxyInfo.flags |= info.flags;
// For each info, populate default properties like ResolveInfo.
setDefaultPropertiesIfNullLocked(info);
}
proxyInfo.setAccessibilityTool(isAccessibilityTool);
proxyInfo.setInteractiveUiTimeoutMillis(interactiveUiTimeout);
proxyInfo.setNonInteractiveUiTimeoutMillis(nonInteractiveUiTimeout);
mInteractiveTimeout = interactiveUiTimeout;
mNonInteractiveTimeout = nonInteractiveUiTimeout;
// If any one service info doesn't set package names, i.e. if it's interested in all
// apps, the proxy shouldn't filter by package name even if some infos specify this.
if (hasNullPackagesNames) {
proxyInfo.packageNames = null;
} else {
proxyInfo.packageNames = packageNames.toArray(new String[0]);
}
// Update connection with mAccessibilityServiceInfo values.
setDynamicallyConfigurableProperties(proxyInfo);
// Notify manager service.
mSystemSupport.onProxyChanged(mDeviceId);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private void setDefaultPropertiesIfNullLocked(AccessibilityServiceInfo info) {
final String componentClassDisplayName = PROXY_COMPONENT_CLASS_NAME + mDisplayId;
// Populate the properties that can't be null, since this may cause crashes in apps that
// assume these are populated by an installed package.
if (info.getResolveInfo() == null) {
final ResolveInfo resolveInfo = new ResolveInfo();
final ServiceInfo serviceInfo = new ServiceInfo();
final ApplicationInfo applicationInfo = new ApplicationInfo();
serviceInfo.packageName = PROXY_COMPONENT_PACKAGE_NAME;
serviceInfo.name = componentClassDisplayName;
applicationInfo.processName = PROXY_COMPONENT_PACKAGE_NAME;
applicationInfo.className = componentClassDisplayName;
resolveInfo.serviceInfo = serviceInfo;
serviceInfo.applicationInfo = applicationInfo;
info.setResolveInfo(resolveInfo);
}
if (info.getComponentName() == null) {
info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME,
componentClassDisplayName));
}
}
@Override
public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() {
synchronized (mLock) {
return mInstalledAndEnabledServices;
}
}
@Override
public AccessibilityWindowInfo.WindowListSparseArray getWindows() {
final AccessibilityWindowInfo.WindowListSparseArray allWindows = super.getWindows();
AccessibilityWindowInfo.WindowListSparseArray displayWindows = new
AccessibilityWindowInfo.WindowListSparseArray();
// Filter here so A11yInteractionClient will not cache all the windows belonging to other
// proxy connections.
displayWindows.put(mDisplayId, allWindows.get(mDisplayId, Collections.emptyList()));
return displayWindows;
}
@Override
public void setFocusAppearance(int strokeWidth, int color) {
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return;
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return;
}
if (getFocusStrokeWidthLocked() == strokeWidth && getFocusColorLocked() == color) {
return;
}
mFocusStrokeWidth = strokeWidth;
mFocusColor = color;
mSystemSupport.onProxyChanged(mDeviceId);
}
}
/**
* Gets the stroke width of the focus rectangle.
* @return The stroke width.
*/
public int getFocusStrokeWidthLocked() {
return mFocusStrokeWidth;
}
/**
* Gets the color of the focus rectangle.
* @return The color.
*/
public int getFocusColorLocked() {
return mFocusColor;
}
@Override
int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType,
mDisplayId);
// If the caller is a proxy and the found window doesn't belong to a proxy display
// (or vice versa), then return null. This doesn't work if there are multiple active
// proxys, but in the future this code shouldn't be needed if input focus
// properly split. (so we will deal with the issues if we see them).
//TODO(254545943): Remove this when there is user and proxy separation of input
if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
return focusedWindowId;
}
return windowId;
}
@Override
public void binderDied() {
}
@Override
protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
// Don't need to check for earlier APIs.
return true;
}
@Override
protected boolean hasRightsToCurrentUserLocked() {
// TODO(250929565): Proxy access is not currently determined by user. Adjust in refactoring.
return true;
}
/** @throws UnsupportedOperationException since a proxy does not need key events */
@Override
public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("onKeyEvent is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
@Override
public boolean isCapturingFingerprintGestures() throws UnsupportedOperationException {
throw new UnsupportedOperationException("isCapturingFingerprintGestures is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
@Override
public void onFingerprintGestureDetectionActiveChanged(boolean active)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("onFingerprintGestureDetectionActiveChanged is not"
+ " supported");
}
/** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
@Override
public void onFingerprintGesture(int gesture) throws UnsupportedOperationException {
throw new UnsupportedOperationException("onFingerprintGesture is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
@Override
public boolean isFingerprintGestureDetectionAvailable() throws UnsupportedOperationException {
throw new UnsupportedOperationException("isFingerprintGestureDetectionAvailable is not"
+ " supported");
}
/** @throws UnsupportedOperationException since a proxy is not a Service */
@Override
public void onServiceConnected(ComponentName name, IBinder service)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("onServiceConnected is not supported");
}
/** @throws UnsupportedOperationException since a proxy is not a Service */
@Override
public void onServiceDisconnected(ComponentName name)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("onServiceDisconnected is not supported");
}
/** @throws UnsupportedOperationException since a proxy should use
* setInstalledAndEnabledServices*/
@Override
public void setServiceInfo(AccessibilityServiceInfo info)
throws UnsupportedOperationException {
// TODO(241429275): Ensure getServiceInfo is called appropriately for a proxy or is a no-op.
throw new UnsupportedOperationException("setServiceInfo is not supported");
}
/** @throws UnsupportedOperationException since a proxy should use A11yManager#unregister */
@Override
public void disableSelf() throws UnsupportedOperationException {
// A proxy uses A11yManager#unregister to turn itself off.
throw new UnsupportedOperationException("disableSelf is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not have global system access */
@Override
public boolean performGlobalAction(int action) throws UnsupportedOperationException {
throw new UnsupportedOperationException("performGlobalAction is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need key events */
@Override
public void setOnKeyEventResult(boolean handled, int sequence)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setOnKeyEventResult is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not have global system access */
@Override
public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions()
throws UnsupportedOperationException {
throw new UnsupportedOperationException("getSystemActions is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Nullable
@Override
public MagnificationConfig getMagnificationConfig(int displayId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("getMagnificationConfig is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public float getMagnificationScale(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("getMagnificationScale is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public float getMagnificationCenterX(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("getMagnificationCenterX is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public float getMagnificationCenterY(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("getMagnificationCenterY is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public Region getMagnificationRegion(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("getMagnificationRegion is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public Region getCurrentMagnificationRegion(int displayId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("getCurrentMagnificationRegion is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public boolean resetMagnification(int displayId, boolean animate)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("resetMagnification is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public boolean resetCurrentMagnification(int displayId, boolean animate)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("resetCurrentMagnification is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public boolean setMagnificationConfig(int displayId,
@androidx.annotation.NonNull MagnificationConfig config, boolean animate)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setMagnificationConfig is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public void setMagnificationCallbackEnabled(int displayId, boolean enabled)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setMagnificationCallbackEnabled is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@Override
public boolean isMagnificationCallbackEnabled(int displayId) {
throw new UnsupportedOperationException("isMagnificationCallbackEnabled is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need IME access*/
@Override
public boolean setSoftKeyboardShowMode(int showMode) throws UnsupportedOperationException {
throw new UnsupportedOperationException("setSoftKeyboardShowMode is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need IME access */
@Override
public int getSoftKeyboardShowMode() throws UnsupportedOperationException {
throw new UnsupportedOperationException("getSoftKeyboardShowMode is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need IME access */
@Override
public void setSoftKeyboardCallbackEnabled(boolean enabled)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setSoftKeyboardCallbackEnabled is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need IME access */
@Override
public boolean switchToInputMethod(String imeId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("switchToInputMethod is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need IME access */
@Override
public int setInputMethodEnabled(String imeId, boolean enabled)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setInputMethodEnabled is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need access to the shortcut */
@Override
public boolean isAccessibilityButtonAvailable() throws UnsupportedOperationException {
throw new UnsupportedOperationException("isAccessibilityButtonAvailable is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
@Override
public void sendGesture(int sequence, ParceledListSlice gestureSteps)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("sendGesture is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
@Override
public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("dispatchGesture is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need access to screenshots */
@Override
public void takeScreenshot(int displayId, RemoteCallback callback)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("takeScreenshot is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
@Override
public void setGestureDetectionPassthroughRegion(int displayId, Region region)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setGestureDetectionPassthroughRegion is not"
+ " supported");
}
/** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
@Override
public void setTouchExplorationPassthroughRegion(int displayId, Region region)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setTouchExplorationPassthroughRegion is not"
+ " supported");
}
/** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
@Override
public void setServiceDetectsGesturesEnabled(int displayId, boolean mode)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("setServiceDetectsGesturesEnabled is not"
+ " supported");
}
/** @throws UnsupportedOperationException since a proxy does not need touch input access */
@Override
public void requestTouchExploration(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("requestTouchExploration is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need touch input access */
@Override
public void requestDragging(int displayId, int pointerId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("requestDragging is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need touch input access */
@Override
public void requestDelegating(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("requestDelegating is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need touch input access */
@Override
public void onDoubleTap(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("onDoubleTap is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need touch input access */
@Override
public void onDoubleTapAndHold(int displayId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("onDoubleTapAndHold is not supported");
}
/** @throws UnsupportedOperationException since a proxy does not need touch input access */
@Override
public void setAnimationScale(float scale) throws UnsupportedOperationException {
throw new UnsupportedOperationException("setAnimationScale is not supported");
}
public int getInteractiveTimeout() {
return mInteractiveTimeout;
}
public int getNonInteractiveTimeout() {
return mNonInteractiveTimeout;
}
/**
* Returns true if a timeout was updated.
*/
public boolean updateTimeouts(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
final int newInteractiveUiTimeout = interactiveUiTimeout != 0
? interactiveUiTimeout
: mAccessibilityServiceInfo.getInteractiveUiTimeoutMillis();
final int newNonInteractiveUiTimeout = nonInteractiveUiTimeout != 0
? nonInteractiveUiTimeout
: mAccessibilityServiceInfo.getNonInteractiveUiTimeoutMillis();
boolean updated = false;
if (mInteractiveTimeout != newInteractiveUiTimeout) {
mInteractiveTimeout = newInteractiveUiTimeout;
updated = true;
}
if (mNonInteractiveTimeout != newNonInteractiveUiTimeout) {
mNonInteractiveTimeout = newNonInteractiveUiTimeout;
updated = true;
}
return updated;
}
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
synchronized (mLock) {
pw.append("Proxy[displayId=" + mDisplayId);
pw.append(", deviceId=" + mDeviceId);
pw.append(", feedbackType"
+ AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
pw.append(", eventTypes="
+ AccessibilityEvent.eventTypeToString(mEventTypes));
pw.append(", notificationTimeout=" + mNotificationTimeout);
pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveTimeout));
pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveTimeout));
pw.append(", focusStrokeWidth=").append(String.valueOf(mFocusStrokeWidth));
pw.append(", focusColor=").append(String.valueOf(mFocusColor));
pw.append(", installedAndEnabledServiceCount=").append(String.valueOf(
mInstalledAndEnabledServices.size()));
pw.append(", installedAndEnabledServices=").append(
mInstalledAndEnabledServices.toString());
pw.append("]");
}
}
}