diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 483defab43c90..0a4e822c8c7d5 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -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; @@ -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; @@ -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; @@ -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(); - } - } - wm.setDefaultToken(token); + @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); } /** @@ -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); + } + } + } } diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java index c65ef9a56cd89..53ba140e6aad7 100644 --- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java @@ -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; @@ -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; @@ -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(); } @@ -78,14 +106,33 @@ public class AccessibilityServiceTest { private @Mock IBinder mMockIBinder; private IAccessibilityServiceClient mServiceInterface; private AccessibilityServiceTestClass mService; + private final SparseArray 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 @@ -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(); + } + } }