Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e73bfc2 authored by Jacky Kao's avatar Jacky Kao
Browse files

Allow A11yService to customize accessibility focus

1. Define and implement new APIs to set and get the stroke width
and color of the A11y focus rectangle.
2. New an AccessibilityFocusApperanceData variable with the default
value in the A11yUserState class, and it would be used in the A11y
framework.
3. When A11y services sets the stroke width and color of the focus
rectangle, A11yUserState will apply the same value through
A11yServiceConnection and notify this change to A11yManager.
4. When A11y services customized the data is disabled, reseting the
data in the A11yUserState and notifying this change to A11yManager.
5. When the ViewRootImpl gets the A11yFocusDrawable, it changes the
strokewidth and the color of this drawable based on the value from
A11yManager.

Bug: 141144573
Test: a11y CTS & unit tests

Change-Id: I1493f17f7cc25744e1435c9070218c6f7efa8bcf
parent 0e8e23d8
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -2883,6 +2883,7 @@ package android.accessibilityservice {
    method protected void onServiceConnected();
    method public void onSystemActionsChanged();
    method public final boolean performGlobalAction(int);
    method public void setAccessibilityFocusAppearance(int, @ColorInt int);
    method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
    method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
    method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
@@ -54611,6 +54612,8 @@ package android.view.accessibility {
    method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
    method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
    method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
    method @ColorInt public int getAccessibilityFocusColor();
    method public int getAccessibilityFocusStrokeWidth();
    method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
    method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
    method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
+22 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.accessibilityservice;

import android.accessibilityservice.GestureDescription.MotionEventGenerator;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -2117,6 +2118,27 @@ public abstract class AccessibilityService extends Service {
        }
    }

    /**
     * Sets the strokeWidth and color of the accessibility focus rectangle.
     *
     * @param strokeWidth The stroke width of the rectangle in pixels.
     *                    Setting this value to zero results in no focus rectangle being drawn.
     * @param color The color of the rectangle.
     */
    public void setAccessibilityFocusAppearance(int strokeWidth, @ColorInt int color) {
        IAccessibilityServiceConnection connection =
                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
        if (connection != null) {
            try {
                connection.setFocusAppearance(strokeWidth, color);
            } catch (RemoteException re) {
                Log.w(LOG_TAG, "Error while setting the strokeWidth and color of the "
                        + "accessibility focus rectangle", re);
                re.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Implement to return the implementation of the internal accessibility
     * service interface.
+2 −0
Original line number Diff line number Diff line
@@ -115,4 +115,6 @@ interface IAccessibilityServiceConnection {
    void setGestureDetectionPassthroughRegion(int displayId, in Region region);

    void setTouchExplorationPassthroughRegion(int displayId, in Region region);

    void setFocusAppearance(int strokeWidth, int color);
}
+9 −0
Original line number Diff line number Diff line
@@ -119,6 +119,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.input.InputManager;
@@ -4414,6 +4415,14 @@ public final class ViewRootImpl implements ViewParent,
                        mView.mContext.getDrawable(value.resourceId);
            }
        }
        // Sets the focus appearance data into the accessibility focus drawable.
        if (mAttachInfo.mAccessibilityFocusDrawable instanceof GradientDrawable) {
            final GradientDrawable drawable =
                    (GradientDrawable) mAttachInfo.mAccessibilityFocusDrawable;
            drawable.setStroke(mAccessibilityManager.getAccessibilityFocusStrokeWidth(),
                    mAccessibilityManager.getAccessibilityFocusColor());
        }

        return mAttachInfo.mAccessibilityFocusDrawable;
    }

+87 −2
Original line number Diff line number Diff line
@@ -19,9 +19,11 @@ package android.view.accessibility;
import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;

import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -59,6 +61,7 @@ import android.view.IWindow;
import android.view.View;
import android.view.accessibility.AccessibilityEvent.EventType;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;

@@ -233,6 +236,11 @@ public final class AccessibilityManager {

    private int mPerformingAction = 0;

    /** The stroke width of the focus rectangle in pixels */
    private int mFocusStrokeWidth;
    /** The color of the focus rectangle */
    private int mFocusColor;

    @UnsupportedAppUsage
    private final ArrayMap<AccessibilityStateChangeListener, Handler>
            mAccessibilityStateChangeListeners = new ArrayMap<>();
@@ -410,6 +418,13 @@ public final class AccessibilityManager {
        public void setRelevantEventTypes(int eventTypes) {
            mRelevantEventTypes = eventTypes;
        }

        @Override
        public void setFocusAppearance(int strokeWidth, int color) {
            synchronized (mLock) {
                updateFocusAppearanceLocked(strokeWidth, color);
            }
        }
    };

    /**
@@ -457,6 +472,7 @@ public final class AccessibilityManager {
        mHandler = new Handler(context.getMainLooper(), mCallback);
        mUserId = userId;
        synchronized (mLock) {
            initialFocusAppearanceLocked(context.getResources());
            tryConnectToServiceLocked(service);
        }
    }
@@ -464,20 +480,28 @@ public final class AccessibilityManager {
    /**
     * Create an instance.
     *
     * @param context A {@link Context}.
     * @param handler The handler to use
     * @param service An interface to the backing service.
     * @param userId User id under which to run.
     * @param serviceConnect {@code true} to connect the service or
     *                       {@code false} not to connect the service.
     *
     * @hide
     */
    public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
    @VisibleForTesting
    public AccessibilityManager(Context context, Handler handler, IAccessibilityManager service,
            int userId, boolean serviceConnect) {
        mCallback = new MyCallback();
        mHandler = handler;
        mUserId = userId;
        synchronized (mLock) {
            initialFocusAppearanceLocked(context.getResources());
            if (serviceConnect) {
                tryConnectToServiceLocked(service);
            }
        }
    }

    /**
     * @hide
@@ -953,6 +977,32 @@ public final class AccessibilityManager {
        return recommendedTimeout;
    }

    /**
     * Gets the strokeWidth of the focus rectangle. This value can be set by
     * {@link AccessibilityService}.
     *
     * @return The strokeWidth of the focus rectangle in pixels.
     *
     */
    public int getAccessibilityFocusStrokeWidth() {
        synchronized (mLock) {
            return mFocusStrokeWidth;
        }
    }

    /**
     * Gets the color of the focus rectangle. This value can be set by
     * {@link AccessibilityService}.
     *
     * @return The color of the focus rectangle.
     *
     */
    public @ColorInt int getAccessibilityFocusColor() {
        synchronized (mLock) {
            return mFocusColor;
        }
    }

    /**
     * Get the preparers that are registered for an accessibility ID
     *
@@ -1551,6 +1601,7 @@ public final class AccessibilityManager {
            setStateLocked(IntPair.first(userStateAndRelevantEvents));
            mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
            updateUiTimeout(service.getRecommendedTimeoutMillis());
            updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
            mService = service;
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -1634,6 +1685,40 @@ public final class AccessibilityManager {
        mNonInteractiveUiTimeout = IntPair.second(uiTimeout);
    }

    /**
     * Updates the stroke width and color of the focus rectangle.
     *
     * @param strokeWidth The strokeWidth of the focus rectangle.
     * @param color The color of the focus rectangle.
     */
    private void updateFocusAppearanceLocked(int strokeWidth, int color) {
        if (mFocusStrokeWidth == strokeWidth && mFocusColor == color) {
            return;
        }
        mFocusStrokeWidth = strokeWidth;
        mFocusColor = color;
    }

    /**
     * Sets the stroke width and color of the focus rectangle to default value.
     *
     * @param resource The resources.
     */
    private void initialFocusAppearanceLocked(Resources resource) {
        try {
            mFocusStrokeWidth = resource.getDimensionPixelSize(
                    R.dimen.accessibility_focus_highlight_stroke_width);
            mFocusColor = resource.getColor(R.color.accessibility_focus_highlight_color);
        } catch (Resources.NotFoundException re) {
            // Sets the stroke width and color to default value by hardcoded for making
            // the Talkback can work normally.
            mFocusStrokeWidth = (int) (4 * resource.getDisplayMetrics().density);
            mFocusColor = 0xbf39b500;
            Log.e(LOG_TAG, "Error while initialing the focus appearance data then setting to"
                    + " default value by hardcoded", re);
        }
    }

    /**
     * Determines if the accessibility button within the system navigation area is supported.
     *
Loading