Merge "Fix Accessibility apps crash when adding view by" into sc-v2-dev am: 57d4674922

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14580807

Change-Id: Ib2a14de8bcab8256f32f6dbc81ecc02810315b28
This commit is contained in:
Charles Chen
2021-05-20 12:01:59 +00:00
committed by Automerger Merge Worker
2 changed files with 202 additions and 21 deletions

View File

@@ -16,6 +16,8 @@
package android.accessibilityservice; package android.accessibilityservice;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import android.accessibilityservice.GestureDescription.MotionEventGenerator; import android.accessibilityservice.GestureDescription.MotionEventGenerator;
import android.annotation.CallbackExecutor; import android.annotation.CallbackExecutor;
import android.annotation.ColorInt; import android.annotation.ColorInt;
@@ -27,6 +29,7 @@ import android.annotation.TestApi;
import android.app.Service; import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@@ -36,6 +39,7 @@ import android.graphics.Region;
import android.hardware.HardwareBuffer; import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
@@ -961,30 +965,31 @@ public abstract class AccessibilityService extends Service {
} }
} }
@NonNull
@Override @Override
public Context createDisplayContext(Display display) { public Context createDisplayContext(Display display) {
final Context context = super.createDisplayContext(display); return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
final int displayId = display.getDisplayId();
setDefaultTokenInternal(context, displayId);
return context;
} }
private void setDefaultTokenInternal(Context context, int displayId) { @NonNull
final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE); @Override
final IAccessibilityServiceConnection connection = public Context createWindowContext(int type, @Nullable Bundle options) {
AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId); final Context context = super.createWindowContext(type, options);
IBinder token = null; if (type != TYPE_ACCESSIBILITY_OVERLAY) {
if (connection != null) { return context;
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);
} }
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; 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 static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent; 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.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.RemoteException; 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.view.accessibility.AccessibilityEvent;
import android.window.WindowTokenClient;
import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -42,6 +64,8 @@ import org.mockito.MockitoAnnotations;
public class AccessibilityServiceTest { public class AccessibilityServiceTest {
private static final String TAG = "AccessibilityServiceTest"; private static final String TAG = "AccessibilityServiceTest";
private static final int CONNECTION_ID = 1; 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 static class AccessibilityServiceTestClass extends AccessibilityService {
private IAccessibilityServiceClient mCallback; private IAccessibilityServiceClient mCallback;
@@ -49,7 +73,11 @@ public class AccessibilityServiceTest {
AccessibilityServiceTestClass() { AccessibilityServiceTestClass() {
super(); 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(); mLooper = InstrumentationRegistry.getContext().getMainLooper();
} }
@@ -78,14 +106,33 @@ public class AccessibilityServiceTest {
private @Mock IBinder mMockIBinder; private @Mock IBinder mMockIBinder;
private IAccessibilityServiceClient mServiceInterface; private IAccessibilityServiceClient mServiceInterface;
private AccessibilityServiceTestClass mService; private AccessibilityServiceTestClass mService;
private final SparseArray<IBinder> mWindowTokens = new SparseArray<>();
@Before @Before
public void setUp() throws RemoteException { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mService = new AccessibilityServiceTestClass(); mService = new AccessibilityServiceTestClass();
mService.onCreate();
mService.setupCallback(mMockClientForCallback); mService.setupCallback(mMockClientForCallback);
mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent()); mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent());
mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder); 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 @Test
@@ -101,4 +148,79 @@ public class AccessibilityServiceTest {
verify(mMockConnection).getSystemActions(); 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();
}
}
} }