Loading core/java/android/accessibilityservice/AccessibilityService.java +78 −19 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.accessibilityservice; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import android.accessibilityservice.GestureDescription.MotionEventGenerator; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; Loading @@ -27,6 +29,7 @@ import android.annotation.TestApi; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; Loading @@ -36,6 +39,7 @@ import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; Loading Loading @@ -961,30 +965,31 @@ public abstract class AccessibilityService extends Service { } } @NonNull @Override public Context createDisplayContext(Display display) { final Context context = super.createDisplayContext(display); final int displayId = display.getDisplayId(); setDefaultTokenInternal(context, displayId); return context; return new AccessibilityContext(super.createDisplayContext(display), mConnectionId); } private void setDefaultTokenInternal(Context context, int displayId) { final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE); final IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId); IBinder token = null; if (connection != null) { synchronized (mLock) { try { token = connection.getOverlayWindowToken(displayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to get window token", re); re.rethrowFromSystemServer(); @NonNull @Override public Context createWindowContext(int type, @Nullable Bundle options) { final Context context = super.createWindowContext(type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } wm.setDefaultToken(token); @NonNull @Override public Context createWindowContext(@NonNull Display display, int type, @Nullable Bundle options) { final Context context = super.createWindowContext(display, type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } /** Loading Loading @@ -2675,4 +2680,58 @@ public abstract class AccessibilityService extends Service { } } } private static class AccessibilityContext extends ContextWrapper { private final int mConnectionId; private AccessibilityContext(Context base, int connectionId) { super(base); mConnectionId = connectionId; setDefaultTokenInternal(this, getDisplayId()); } @NonNull @Override public Context createDisplayContext(Display display) { return new AccessibilityContext(super.createDisplayContext(display), mConnectionId); } @NonNull @Override public Context createWindowContext(int type, @Nullable Bundle options) { final Context context = super.createWindowContext(type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } @NonNull @Override public Context createWindowContext(@NonNull Display display, int type, @Nullable Bundle options) { final Context context = super.createWindowContext(display, type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } private void setDefaultTokenInternal(Context context, int displayId) { final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService( WINDOW_SERVICE); final IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getConnection(mConnectionId); IBinder token = null; if (connection != null) { try { token = connection.getOverlayWindowToken(displayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to get window token", re); re.rethrowFromSystemServer(); } wm.setDefaultToken(token); } } } } core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java +124 −2 Original line number Diff line number Diff line Loading @@ -16,18 +16,40 @@ package android.accessibilityservice; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.ImageReader; import android.os.Binder; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.SparseArray; import android.view.Display; import android.view.View; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.window.WindowTokenClient; import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -42,6 +64,8 @@ import org.mockito.MockitoAnnotations; public class AccessibilityServiceTest { private static final String TAG = "AccessibilityServiceTest"; private static final int CONNECTION_ID = 1; private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams( TYPE_ACCESSIBILITY_OVERLAY); private static class AccessibilityServiceTestClass extends AccessibilityService { private IAccessibilityServiceClient mCallback; Loading @@ -49,7 +73,11 @@ public class AccessibilityServiceTest { AccessibilityServiceTestClass() { super(); attachBaseContext(InstrumentationRegistry.getContext()); Context context = ApplicationProvider.getApplicationContext(); final Display display = context.getSystemService(DisplayManager.class) .getDisplay(DEFAULT_DISPLAY); attachBaseContext(context.createTokenContext(new WindowTokenClient(), display)); mLooper = InstrumentationRegistry.getContext().getMainLooper(); } Loading Loading @@ -78,14 +106,33 @@ public class AccessibilityServiceTest { private @Mock IBinder mMockIBinder; private IAccessibilityServiceClient mServiceInterface; private AccessibilityServiceTestClass mService; private final SparseArray<IBinder> mWindowTokens = new SparseArray<>(); @Before public void setUp() throws RemoteException { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mService = new AccessibilityServiceTestClass(); mService.onCreate(); mService.setupCallback(mMockClientForCallback); mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent()); mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder); doAnswer(invocation -> { Object[] args = invocation.getArguments(); final int displayId = (int) args[0]; final IBinder token = new Binder(); WindowManagerGlobal.getWindowManagerService().addWindowToken(token, TYPE_ACCESSIBILITY_OVERLAY, displayId, null /* options */); mWindowTokens.put(displayId, token); return token; }).when(mMockConnection).getOverlayWindowToken(anyInt()); } @After public void tearDown() throws Exception { for (int i = mWindowTokens.size() - 1; i >= 0; --i) { WindowManagerGlobal.getWindowManagerService().removeWindowToken( mWindowTokens.valueAt(i), mWindowTokens.keyAt(i)); } } @Test Loading @@ -101,4 +148,79 @@ public class AccessibilityServiceTest { verify(mMockConnection).getSystemActions(); } @Test public void testAddViewWithA11yServiceDerivedDisplayContext() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createDisplayContext(session.getDisplay()); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } @Test public void testAddViewWithA11yServiceDerivedWindowContext() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createDisplayContext(session.getDisplay()) .createWindowContext(TYPE_ACCESSIBILITY_OVERLAY, null /* options */); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } @Test public void testAddViewWithA11yServiceDerivedWindowContextWithDisplay() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createWindowContext(session.getDisplay(), TYPE_ACCESSIBILITY_OVERLAY, null /* options */); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } @Test(expected = WindowManager.BadTokenException.class) public void testAddViewWithA11yServiceDerivedWindowContextWithDifferentType() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createWindowContext(session.getDisplay(), TYPE_APPLICATION_OVERLAY, null /* options */); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } private static class VirtualDisplaySession implements AutoCloseable { private final VirtualDisplay mVirtualDisplay; VirtualDisplaySession() { final DisplayManager displayManager = ApplicationProvider.getApplicationContext() .getSystemService(DisplayManager.class); final int width = 800; final int height = 480; final int density = 160; ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2 /* maxImages */); mVirtualDisplay = displayManager.createVirtualDisplay( TAG, width, height, density, reader.getSurface(), VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); } private Display getDisplay() { return mVirtualDisplay.getDisplay(); } @Override public void close() throws Exception { mVirtualDisplay.release(); } } } Loading
core/java/android/accessibilityservice/AccessibilityService.java +78 −19 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.accessibilityservice; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import android.accessibilityservice.GestureDescription.MotionEventGenerator; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; Loading @@ -27,6 +29,7 @@ import android.annotation.TestApi; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; Loading @@ -36,6 +39,7 @@ import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; Loading Loading @@ -961,30 +965,31 @@ public abstract class AccessibilityService extends Service { } } @NonNull @Override public Context createDisplayContext(Display display) { final Context context = super.createDisplayContext(display); final int displayId = display.getDisplayId(); setDefaultTokenInternal(context, displayId); return context; return new AccessibilityContext(super.createDisplayContext(display), mConnectionId); } private void setDefaultTokenInternal(Context context, int displayId) { final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE); final IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId); IBinder token = null; if (connection != null) { synchronized (mLock) { try { token = connection.getOverlayWindowToken(displayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to get window token", re); re.rethrowFromSystemServer(); @NonNull @Override public Context createWindowContext(int type, @Nullable Bundle options) { final Context context = super.createWindowContext(type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } wm.setDefaultToken(token); @NonNull @Override public Context createWindowContext(@NonNull Display display, int type, @Nullable Bundle options) { final Context context = super.createWindowContext(display, type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } /** Loading Loading @@ -2675,4 +2680,58 @@ public abstract class AccessibilityService extends Service { } } } private static class AccessibilityContext extends ContextWrapper { private final int mConnectionId; private AccessibilityContext(Context base, int connectionId) { super(base); mConnectionId = connectionId; setDefaultTokenInternal(this, getDisplayId()); } @NonNull @Override public Context createDisplayContext(Display display) { return new AccessibilityContext(super.createDisplayContext(display), mConnectionId); } @NonNull @Override public Context createWindowContext(int type, @Nullable Bundle options) { final Context context = super.createWindowContext(type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } @NonNull @Override public Context createWindowContext(@NonNull Display display, int type, @Nullable Bundle options) { final Context context = super.createWindowContext(display, type, options); if (type != TYPE_ACCESSIBILITY_OVERLAY) { return context; } return new AccessibilityContext(context, mConnectionId); } private void setDefaultTokenInternal(Context context, int displayId) { final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService( WINDOW_SERVICE); final IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getConnection(mConnectionId); IBinder token = null; if (connection != null) { try { token = connection.getOverlayWindowToken(displayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to get window token", re); re.rethrowFromSystemServer(); } wm.setDefaultToken(token); } } } }
core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java +124 −2 Original line number Diff line number Diff line Loading @@ -16,18 +16,40 @@ package android.accessibilityservice; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.ImageReader; import android.os.Binder; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.SparseArray; import android.view.Display; import android.view.View; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.window.WindowTokenClient; import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -42,6 +64,8 @@ import org.mockito.MockitoAnnotations; public class AccessibilityServiceTest { private static final String TAG = "AccessibilityServiceTest"; private static final int CONNECTION_ID = 1; private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams( TYPE_ACCESSIBILITY_OVERLAY); private static class AccessibilityServiceTestClass extends AccessibilityService { private IAccessibilityServiceClient mCallback; Loading @@ -49,7 +73,11 @@ public class AccessibilityServiceTest { AccessibilityServiceTestClass() { super(); attachBaseContext(InstrumentationRegistry.getContext()); Context context = ApplicationProvider.getApplicationContext(); final Display display = context.getSystemService(DisplayManager.class) .getDisplay(DEFAULT_DISPLAY); attachBaseContext(context.createTokenContext(new WindowTokenClient(), display)); mLooper = InstrumentationRegistry.getContext().getMainLooper(); } Loading Loading @@ -78,14 +106,33 @@ public class AccessibilityServiceTest { private @Mock IBinder mMockIBinder; private IAccessibilityServiceClient mServiceInterface; private AccessibilityServiceTestClass mService; private final SparseArray<IBinder> mWindowTokens = new SparseArray<>(); @Before public void setUp() throws RemoteException { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mService = new AccessibilityServiceTestClass(); mService.onCreate(); mService.setupCallback(mMockClientForCallback); mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent()); mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder); doAnswer(invocation -> { Object[] args = invocation.getArguments(); final int displayId = (int) args[0]; final IBinder token = new Binder(); WindowManagerGlobal.getWindowManagerService().addWindowToken(token, TYPE_ACCESSIBILITY_OVERLAY, displayId, null /* options */); mWindowTokens.put(displayId, token); return token; }).when(mMockConnection).getOverlayWindowToken(anyInt()); } @After public void tearDown() throws Exception { for (int i = mWindowTokens.size() - 1; i >= 0; --i) { WindowManagerGlobal.getWindowManagerService().removeWindowToken( mWindowTokens.valueAt(i), mWindowTokens.keyAt(i)); } } @Test Loading @@ -101,4 +148,79 @@ public class AccessibilityServiceTest { verify(mMockConnection).getSystemActions(); } @Test public void testAddViewWithA11yServiceDerivedDisplayContext() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createDisplayContext(session.getDisplay()); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } @Test public void testAddViewWithA11yServiceDerivedWindowContext() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createDisplayContext(session.getDisplay()) .createWindowContext(TYPE_ACCESSIBILITY_OVERLAY, null /* options */); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } @Test public void testAddViewWithA11yServiceDerivedWindowContextWithDisplay() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createWindowContext(session.getDisplay(), TYPE_ACCESSIBILITY_OVERLAY, null /* options */); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } @Test(expected = WindowManager.BadTokenException.class) public void testAddViewWithA11yServiceDerivedWindowContextWithDifferentType() throws Exception { try (VirtualDisplaySession session = new VirtualDisplaySession()) { final Context context = mService.createWindowContext(session.getDisplay(), TYPE_APPLICATION_OVERLAY, null /* options */); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> context.getSystemService(WindowManager.class) .addView(new View(context), mParams) ); } } private static class VirtualDisplaySession implements AutoCloseable { private final VirtualDisplay mVirtualDisplay; VirtualDisplaySession() { final DisplayManager displayManager = ApplicationProvider.getApplicationContext() .getSystemService(DisplayManager.class); final int width = 800; final int height = 480; final int density = 160; ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2 /* maxImages */); mVirtualDisplay = displayManager.createVirtualDisplay( TAG, width, height, density, reader.getSurface(), VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); } private Display getDisplay() { return mVirtualDisplay.getDisplay(); } @Override public void close() throws Exception { mVirtualDisplay.release(); } } }