blob: 53ba140e6aad73272b4843b781cfc3881bb2aab3 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Unit tests for AccessibilityService.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
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;
private Looper mLooper;
AccessibilityServiceTestClass() {
super();
Context context = ApplicationProvider.getApplicationContext();
final Display display = context.getSystemService(DisplayManager.class)
.getDisplay(DEFAULT_DISPLAY);
attachBaseContext(context.createTokenContext(new WindowTokenClient(), display));
mLooper = InstrumentationRegistry.getContext().getMainLooper();
}
public void setupCallback(IAccessibilityServiceClient callback) {
mCallback = callback;
}
public Looper getMainLooper() {
return mLooper;
}
public void onAccessibilityEvent(AccessibilityEvent event) { }
public void onInterrupt() { }
@Override
public void onSystemActionsChanged() {
try {
if (mCallback != null) mCallback.onSystemActionsChanged();
} catch (RemoteException e) {
}
}
}
private @Mock IAccessibilityServiceClient mMockClientForCallback;
private @Mock IAccessibilityServiceConnection mMockConnection;
private @Mock IBinder mMockIBinder;
private IAccessibilityServiceClient mServiceInterface;
private AccessibilityServiceTestClass mService;
private final SparseArray<IBinder> mWindowTokens = new SparseArray<>();
@Before
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
public void testOnSystemActionsChanged() throws RemoteException {
mServiceInterface.onSystemActionsChanged();
verify(mMockClientForCallback).onSystemActionsChanged();
}
@Test
public void testGetSystemActions() throws RemoteException {
mService.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();
}
}
}