Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 375343f2 authored by Charles Chen's avatar Charles Chen Committed by Automerger Merge Worker
Browse files

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

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

Change-Id: Ib2a14de8bcab8256f32f6dbc81ecc02810315b28
parents 5a60e178 57d46749
Loading
Loading
Loading
Loading
+78 −19
Original line number Diff line number Diff line
@@ -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();
    @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);
    }

    /**
@@ -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);
            }
        }
    }
}
+124 −2
Original line number Diff line number Diff line
@@ -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();
        }
    }
}