From 9e73bfc2f305fadb90c919bb4c0191f2f2031057 Mon Sep 17 00:00:00 2001 From: Jacky Kao Date: Tue, 17 Nov 2020 16:14:48 +0800 Subject: [PATCH] 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 --- core/api/current.txt | 3 + .../AccessibilityService.java | 22 +++++ .../IAccessibilityServiceConnection.aidl | 2 + core/java/android/view/ViewRootImpl.java | 9 ++ .../accessibility/AccessibilityManager.java | 89 ++++++++++++++++++- .../accessibility/IAccessibilityManager.aidl | 4 + .../IAccessibilityManagerClient.aidl | 2 + .../drawable/view_accessibility_focused.xml | 4 +- core/res/res/values/colors.xml | 2 +- core/res/res/values/dimens.xml | 3 + core/res/res/values/symbols.xml | 5 ++ .../AccessibilityManagerTest.java | 25 +++++- .../AccessibilityServiceConnectionImpl.java | 2 + .../AccessibilityShortcutControllerTest.java | 4 +- ...bstractAccessibilityServiceConnection.java | 3 + .../AccessibilityManagerService.java | 39 ++++++++ .../AccessibilityServiceConnection.java | 32 +++++++ .../accessibility/AccessibilityUserState.java | 53 +++++++++++ .../AccessibilityManagerServiceTest.java | 6 ++ .../AccessibilityUserStateTest.java | 33 +++++++ .../notification/BuzzBeepBlinkTest.java | 3 +- 21 files changed, 337 insertions(+), 8 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index f748c24e91818..0ecf95d043599 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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 getAccessibilityServiceList(); method public java.util.List getEnabledAccessibilityServiceList(int); method public java.util.List getInstalledAccessibilityServiceList(); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 0ad9e446dfc77..8e50184c96e0b 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -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. diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 0b3b9b2ecae19..ab21dc9f14ade 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -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); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 349b84899f9da..2d633cbeb3537 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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; } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index a8534faf43e5d..56dcd5951e5e0 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -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 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,18 +480,26 @@ 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) { - tryConnectToServiceLocked(service); + initialFocusAppearanceLocked(context.getResources()); + if (serviceConnect) { + tryConnectToServiceLocked(service); + } } } @@ -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. * diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 5d3c72015ee3b..c71ea53c414d9 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -92,4 +92,8 @@ interface IAccessibilityManager { void associateEmbeddedHierarchy(IBinder host, IBinder embedded); void disassociateEmbeddedHierarchy(IBinder token); + + int getFocusStrokeWidth(); + + int getFocusColor(); } diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl index 94b9ad1c32790..041399ccb8ec4 100644 --- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl @@ -29,4 +29,6 @@ oneway interface IAccessibilityManagerClient { void notifyServicesStateChanged(long updatedUiTimeout); void setRelevantEventTypes(int eventTypes); + + void setFocusAppearance(int strokeWidth, int color); } diff --git a/core/res/res/drawable/view_accessibility_focused.xml b/core/res/res/drawable/view_accessibility_focused.xml index 025916b35f402..aa3031e5cf26b 100644 --- a/core/res/res/drawable/view_accessibility_focused.xml +++ b/core/res/res/drawable/view_accessibility_focused.xml @@ -17,8 +17,8 @@ + android:width="@dimen/accessibility_focus_highlight_stroke_width" + android:color="@color/accessibility_focus_highlight_color" /> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 0079d8cd02766..110e77a8aa053 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -159,7 +159,7 @@ #ffffffff #ff35b5e5 - #bf39b500 + #bf39b500 #4dffeb3b #ff607D8B diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 4bcabff109ea3..05db741643e8c 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -553,6 +553,9 @@ 4dip + + 4dp + 8dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index cd00fbf204cfb..8e4e9af8f4ad9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4115,4 +4115,9 @@ + + + + + diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index 8e2490789a6f0..75116d8c6df26 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -38,6 +38,7 @@ import android.os.UserHandle; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; import com.android.internal.util.IntPair; import com.android.server.accessibility.test.MessageCapturingHandler; @@ -73,12 +74,18 @@ public class AccessibilityManagerTest { @Mock private IAccessibilityManager mMockService; private MessageCapturingHandler mHandler; private Instrumentation mInstrumentation; + private int mFocusStrokeWidthDefaultValue; + private int mFocusColorDefaultValue; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mHandler = new MessageCapturingHandler(null); mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mFocusStrokeWidthDefaultValue = mInstrumentation.getContext().getResources() + .getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width); + mFocusColorDefaultValue = mInstrumentation.getContext().getResources().getColor( + R.color.accessibility_focus_highlight_color); } @After @@ -94,8 +101,12 @@ public class AccessibilityManagerTest { when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt())) .thenReturn(serviceReturnValue); + when(mMockService.getFocusStrokeWidth()).thenReturn(mFocusStrokeWidthDefaultValue); + when(mMockService.getFocusColor()).thenReturn(mFocusColorDefaultValue); + AccessibilityManager manager = - new AccessibilityManager(mHandler, mMockService, UserHandle.USER_CURRENT); + new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService, + UserHandle.USER_CURRENT, true); verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt()); mHandler.setCallback(manager.getCallback()); @@ -205,4 +216,16 @@ public class AccessibilityManagerTest { verify(mMockService).setWindowMagnificationConnection(connection); } + + @Test + public void testGetDefaultValueOfFocusAppearanceData() { + AccessibilityManager manager = + new AccessibilityManager(mInstrumentation.getContext(), mHandler, null, + UserHandle.USER_CURRENT, false); + + assertEquals(mFocusStrokeWidthDefaultValue, + manager.getAccessibilityFocusStrokeWidth()); + assertEquals(mFocusColorDefaultValue, + manager.getAccessibilityFocusColor()); + } } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 75a7504cac2fc..cfdb2b769a08e 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -161,4 +161,6 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void setTouchExplorationPassthroughRegion(int displayId, Region region) {} public void setGestureDetectionPassthroughRegion(int displayId, Region region) {} + + public void setFocusAppearance(int strokeWidth, int color) {} } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index c17c36eba2dc5..6baf3056be320 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -68,6 +68,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; import android.widget.Toast; +import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; @@ -148,7 +149,8 @@ public class AccessibilityShortcutControllerTest { // Use the extra level of indirection in the object to mock framework objects AccessibilityManager accessibilityManager = - new AccessibilityManager(mHandler, mAccessibilityManagerService, 0); + new AccessibilityManager(InstrumentationRegistry.getContext(), mHandler, + mAccessibilityManagerService, 0, true); when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext)) .thenReturn(accessibilityManager); when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE)) diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index d6d4e4f6c746c..8f093c7e66746 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1797,4 +1797,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public void setTouchExplorationPassthroughRegion(int displayId, Region region) { mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region); } + + @Override + public void setFocusAppearance(int strokeWidth, int color) { } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index be2f8f16a4120..c6919ad245723 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1968,6 +1968,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Update the capabilities before the mode. updateMagnificationCapabilitiesSettingsChangeLocked(userState); updateMagnificationModeChangeSettingsLocked(userState); + updateFocusAppearanceDataLocked(userState); } private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) { @@ -3012,6 +3013,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + /** + * Gets the stroke width of the focus rectangle. + * @return The stroke width. + */ + public int getFocusStrokeWidth() { + synchronized (mLock) { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + + return userState.getFocusStrokeWidthLocked(); + } + } + + /** + * Gets the color of the focus rectangle. + * @return The color. + */ + public int getFocusColor() { + synchronized (mLock) { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + + return userState.getFocusColorLocked(); + } + } + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; @@ -3623,4 +3648,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } + + private void updateFocusAppearanceDataLocked(AccessibilityUserState userState) { + if (userState.mUserId != mCurrentUserId) { + return; + } + + mMainHandler.post(() -> { + broadcastToClients(userState, ignoreRemoteException(client -> { + client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(), + userState.getFocusColorLocked()); + })); + }); + + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index e48d11d17f40d..5d67992316a24 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -120,6 +120,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState == null) return; userState.removeServiceLocked(this); + userState.resetFocusAppearanceLocked(); + mSystemSupport.onClientChangeLocked(false); mSystemSupport.getFullScreenMagnificationController().resetAllIfNeeded(mId); mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(), -1, userState.mUserId); @@ -144,6 +146,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } finally { Binder.restoreCallingIdentity(identity); } + userState.resetFocusAppearanceLocked(); mSystemSupport.onClientChangeLocked(false); } } @@ -310,6 +313,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState != null) { userState.serviceDisconnectedLocked(this); + userState.resetFocusAppearanceLocked(); } resetLocked(); mSystemSupport.getFullScreenMagnificationController().resetAllIfNeeded(mId); @@ -391,4 +395,32 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } } + + @Override + public void setFocusAppearance(int strokeWidth, int color) { + AccessibilityUserState userState = mUserStateWeakReference.get(); + if (userState == null) { + return; + } + + synchronized (mLock) { + if (!hasRightsToCurrentUserLocked()) { + return; + } + + if (!mSecurityPolicy.checkAccessibilityAccess(this)) { + return; + } + + if (userState.getFocusStrokeWidthLocked() == strokeWidth + && userState.getFocusColorLocked() == color) { + return; + } + + // Sets the appearance data in the A11yUserState. + userState.setFocusAppearanceLocked(strokeWidth, color); + // Updates the appearance data in the A11yManager. + mSystemSupport.onClientChangeLocked(false); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 240c7ff061c83..90e2fdfa2f033 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -45,6 +45,7 @@ import android.util.Slog; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; +import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; import java.io.FileDescriptor; @@ -122,6 +123,15 @@ class AccessibilityUserState { // The magnification capabilities used to know magnification mode could be switched. private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + /** The stroke width of the focus rectangle in pixels */ + private int mFocusStrokeWidth; + /** The color of the focus rectangle */ + private int mFocusColor; + // The default value of the focus stroke width. + private final int mFocusStrokeWidthDefaultValue; + // The default value of the focus color. + private final int mFocusColorDefaultValue; + private Context mContext; @SoftKeyboardShowMode @@ -140,6 +150,12 @@ class AccessibilityUserState { mUserId = userId; mContext = context; mServiceInfoChangeListener = serviceInfoChangeListener; + mFocusStrokeWidthDefaultValue = mContext.getResources().getDimensionPixelSize( + R.dimen.accessibility_focus_highlight_stroke_width); + mFocusColorDefaultValue = mContext.getResources().getColor( + R.color.accessibility_focus_highlight_color); + mFocusStrokeWidth = mFocusStrokeWidthDefaultValue; + mFocusColor = mFocusColorDefaultValue; } boolean isHandlingAccessibilityEventsLocked() { @@ -178,6 +194,7 @@ class AccessibilityUserState { mUserNonInteractiveUiTimeout = 0; mUserInteractiveUiTimeout = 0; mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + resetFocusAppearanceLocked(); } void addServiceLocked(AccessibilityServiceConnection serviceConnection) { @@ -880,4 +897,40 @@ class AccessibilityUserState { } return false; } + + /** + * 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; + } + + /** + * Sets 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. + */ + public void setFocusAppearanceLocked(int strokeWidth, int color) { + mFocusStrokeWidth = strokeWidth; + mFocusColor = color; + } + + /** + * Resets the stroke width and color of the focus rectangle to the default value. + * + */ + public void resetFocusAppearanceLocked() { + mFocusStrokeWidth = mFocusStrokeWidthDefaultValue; + mFocusColor = mFocusColorDefaultValue; + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 3f3d5e5106c58..839bc93ba00f4 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -35,6 +35,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Resources; import android.graphics.drawable.Icon; import android.os.IBinder; import android.os.UserHandle; @@ -94,6 +95,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { @Mock private IAccessibilityServiceClient mMockServiceClient; @Mock private WindowMagnificationManager mMockWindowMagnificationMgr; @Mock private MagnificationController mMockMagnificationController; + @Mock private Resources mMockResources; + private AccessibilityUserState mUserState; private MessageCapturingHandler mHandler = new MessageCapturingHandler(null); @@ -121,6 +124,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { mMockA11yDisplayListener, mMockMagnificationController); + mMockResources = mock(Resources.class); + when(mMockContext.getResources()).thenReturn(mMockResources); + final AccessibilityUserState userState = new AccessibilityUserState( mA11yms.getCurrentUserIdLocked(), mMockContext, mA11yms); mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index fbfb0455bd460..81ca92cc3c8cf 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -42,11 +42,16 @@ import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; import android.content.ComponentName; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.testing.DexmakerShareClassLoaderRule; import android.util.ArraySet; +import androidx.test.InstrumentationRegistry; + +import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import org.junit.After; @@ -90,8 +95,13 @@ public class AccessibilityUserStateTest { private AccessibilityUserState mUserState; + private int mFocusStrokeWidthDefaultValue; + private int mFocusColorDefaultValue; + @Before public void setUp() { + final Resources resources = InstrumentationRegistry.getContext().getResources(); + MockitoAnnotations.initMocks(this); FakeSettingsProvider.clearSettingsProvider(); mMockResolver = new MockContentResolver(); @@ -99,6 +109,11 @@ public class AccessibilityUserStateTest { when(mMockContext.getContentResolver()).thenReturn(mMockResolver); when(mMockServiceInfo.getComponentName()).thenReturn(COMPONENT_NAME); when(mMockConnection.getServiceInfo()).thenReturn(mMockServiceInfo); + when(mMockContext.getResources()).thenReturn(resources); + + mFocusStrokeWidthDefaultValue = + resources.getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width); + mFocusColorDefaultValue = resources.getColor(R.color.accessibility_focus_highlight_color); mUserState = new AccessibilityUserState(USER_ID, mMockContext, mMockListener); } @@ -129,6 +144,7 @@ public class AccessibilityUserStateTest { mUserState.setUserNonInteractiveUiTimeoutLocked(30); mUserState.setUserInteractiveUiTimeoutLocked(30); mUserState.setMagnificationModeLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + mUserState.setFocusAppearanceLocked(20, Color.BLUE); mUserState.onSwitchToAnotherUserLocked(); @@ -150,6 +166,8 @@ public class AccessibilityUserStateTest { assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked()); assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, mUserState.getMagnificationModeLocked()); + assertEquals(mFocusStrokeWidthDefaultValue, mUserState.getFocusStrokeWidthLocked()); + assertEquals(mFocusColorDefaultValue, mUserState.getFocusColorLocked()); } @Test @@ -350,6 +368,21 @@ public class AccessibilityUserStateTest { mUserState.getMagnificationModeLocked()); } + @Test + public void setFocusAppearanceData_returnExpectedFocusAppearanceData() { + final int focusStrokeWidthValue = 100; + final int focusColorValue = Color.BLUE; + + assertEquals(mFocusStrokeWidthDefaultValue, mUserState.getFocusStrokeWidthLocked()); + assertEquals(mFocusColorDefaultValue, mUserState.getFocusColorLocked()); + + mUserState.setFocusAppearanceLocked(focusStrokeWidthValue, focusColorValue); + + assertEquals(focusStrokeWidthValue, mUserState.getFocusStrokeWidthLocked()); + assertEquals(focusColorValue, mUserState.getFocusColorLocked()); + + } + private int getSecureIntForUser(String key, int userId) { return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 00f706b13c586..32cb2a3a25403 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -153,7 +153,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { AccessibilityEvent.TYPES_ALL_MASK); when(mAccessibilityService.addClient(any(), anyInt())).thenReturn(serviceReturnValue); AccessibilityManager accessibilityManager = - new AccessibilityManager(Handler.getMain(), mAccessibilityService, 0); + new AccessibilityManager(getContext(), Handler.getMain(), mAccessibilityService, + 0, true); verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt()); assertTrue(accessibilityManager.isEnabled());