Merge "Fix Accessibility apps crash when adding view by" into sc-v2-dev

This commit is contained in:
Charles Chen
2021-05-20 11:44:08 +00:00
committed by Android (Google) Code Review
2 changed files with 202 additions and 21 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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<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
@@ -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();
}
}
}