1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.accessibility.dialog;
18 
19 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
20 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import android.accessibilityservice.AccessibilityServiceInfo;
25 import android.app.AlertDialog;
26 import android.content.Context;
27 import android.os.RemoteException;
28 import android.platform.test.flag.junit.CheckFlagsRule;
29 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
30 import android.testing.AndroidTestingRunner;
31 import android.testing.TestableLooper;
32 import android.view.InputDevice;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.Window;
36 import android.widget.TextView;
37 
38 import androidx.test.platform.app.InstrumentationRegistry;
39 
40 import com.android.internal.R;
41 import com.android.internal.accessibility.TestUtils;
42 
43 import com.google.common.truth.Expect;
44 
45 import org.junit.Before;
46 import org.junit.Rule;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 import org.mockito.MockitoAnnotations;
50 
51 import java.util.concurrent.atomic.AtomicBoolean;
52 
53 /**
54  * Unit Tests for
55  * {@link com.android.internal.accessibility.dialog.AccessibilityServiceWarning}
56  */
57 @RunWith(AndroidTestingRunner.class)
58 @TestableLooper.RunWithLooper
59 public class AccessibilityServiceWarningTest {
60     private static final String A11Y_SERVICE_PACKAGE_LABEL = "TestA11yService";
61     private static final String A11Y_SERVICE_SUMMARY = "TestA11yService summary";
62     private static final String A11Y_SERVICE_COMPONENT_NAME =
63             "fake.package/test.a11yservice.name";
64 
65     private Context mContext;
66     private AccessibilityServiceInfo mAccessibilityServiceInfo;
67     private AtomicBoolean mAllowListener;
68     private AtomicBoolean mDenyListener;
69     private AtomicBoolean mUninstallListener;
70 
71     @Rule
72     public final Expect expect = Expect.create();
73 
74     @Rule
75     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
76 
77     @Before
setUp()78     public void setUp() throws RemoteException {
79         MockitoAnnotations.initMocks(this);
80         mContext = InstrumentationRegistry.getInstrumentation().getContext();
81         mAccessibilityServiceInfo = TestUtils.createFakeServiceInfo(
82                 A11Y_SERVICE_PACKAGE_LABEL,
83                 A11Y_SERVICE_SUMMARY,
84                 A11Y_SERVICE_COMPONENT_NAME,
85                 /* isAlwaysOnService*/ false);
86         mAllowListener = new AtomicBoolean(false);
87         mDenyListener = new AtomicBoolean(false);
88         mUninstallListener = new AtomicBoolean(false);
89     }
90 
91     @Test
createAccessibilityServiceWarningDialog_hasExpectedWindowParams()92     public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams() {
93         final AlertDialog dialog =
94                 AccessibilityServiceWarning.createAccessibilityServiceWarningDialog(
95                         mContext,
96                         mAccessibilityServiceInfo,
97                         null, null, null);
98         final Window dialogWindow = dialog.getWindow();
99         assertThat(dialogWindow).isNotNull();
100 
101         expect.that(dialogWindow.getAttributes().privateFlags
102                 & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(
103                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
104         expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG);
105     }
106 
107     @Test
createAccessibilityServiceWarningDialog_hasExpectedServiceName()108     public void createAccessibilityServiceWarningDialog_hasExpectedServiceName() {
109         final TextView title = createDialogContentView().findViewById(
110                 R.id.accessibility_permissionDialog_title);
111         assertThat(title).isNotNull();
112 
113         assertThat(title.getText().toString()).contains(A11Y_SERVICE_PACKAGE_LABEL);
114     }
115 
116     @Test
createAccessibilityServiceWarningDialog_clickAllow()117     public void createAccessibilityServiceWarningDialog_clickAllow() {
118         final View allowButton = createDialogContentView().findViewById(
119                 R.id.accessibility_permission_enable_allow_button);
120         assertThat(allowButton).isNotNull();
121 
122         allowButton.performClick();
123 
124         expect.that(mAllowListener.get()).isTrue();
125         expect.that(mDenyListener.get()).isFalse();
126         expect.that(mUninstallListener.get()).isFalse();
127     }
128 
129     @Test
createAccessibilityServiceWarningDialog_clickDeny()130     public void createAccessibilityServiceWarningDialog_clickDeny() {
131         final View denyButton = createDialogContentView().findViewById(
132                 R.id.accessibility_permission_enable_deny_button);
133         assertThat(denyButton).isNotNull();
134 
135         denyButton.performClick();
136 
137         expect.that(mAllowListener.get()).isFalse();
138         expect.that(mDenyListener.get()).isTrue();
139         expect.that(mUninstallListener.get()).isFalse();
140     }
141 
142     @Test
createAccessibilityServiceWarningDialog_clickUninstall()143     public void createAccessibilityServiceWarningDialog_clickUninstall() {
144         final View uninstallButton = createDialogContentView().findViewById(
145                 R.id.accessibility_permission_enable_uninstall_button);
146         assertThat(uninstallButton).isNotNull();
147 
148         uninstallButton.performClick();
149 
150         expect.that(mAllowListener.get()).isFalse();
151         expect.that(mDenyListener.get()).isFalse();
152         expect.that(mUninstallListener.get()).isTrue();
153     }
154 
155     @Test
getTouchConsumingListener()156     public void getTouchConsumingListener() {
157         final View allowButton = createDialogContentView().findViewById(
158                 R.id.accessibility_permission_enable_allow_button);
159         assertThat(allowButton).isNotNull();
160         final View.OnTouchListener listener =
161                 AccessibilityServiceWarning.getTouchConsumingListener();
162 
163         expect.that(listener.onTouch(allowButton, createMotionEvent(0))).isFalse();
164         expect.that(listener.onTouch(allowButton,
165                 createMotionEvent(MotionEvent.FLAG_WINDOW_IS_OBSCURED))).isTrue();
166         expect.that(listener.onTouch(allowButton,
167                 createMotionEvent(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED))).isTrue();
168     }
169 
createDialogContentView()170     private View createDialogContentView() {
171         return AccessibilityServiceWarning.createAccessibilityServiceWarningDialogContentView(
172                 mContext,
173                 mAccessibilityServiceInfo,
174                 (v) -> mAllowListener.set(true),
175                 (v) -> mDenyListener.set(true),
176                 (v) -> mUninstallListener.set(true));
177     }
178 
createMotionEvent(int flags)179     private MotionEvent createMotionEvent(int flags) {
180         MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[]{
181                 new MotionEvent.PointerProperties()
182         };
183         MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[]{
184                 new MotionEvent.PointerCoords()
185         };
186         return MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1, props, coords,
187                 0, 0, 0, 0, -1, 0, InputDevice.SOURCE_TOUCHSCREEN, flags);
188     }
189 }
190