1 /*
2  * Copyright (C) 2022 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.systemui.clipboardoverlay;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertTrue;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyString;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.ArgumentMatchers.isNull;
26 import static org.mockito.Mockito.when;
27 
28 import android.app.RemoteAction;
29 import android.content.ClipData;
30 import android.content.ClipDescription;
31 import android.os.PersistableBundle;
32 import android.testing.TestableResources;
33 import android.util.ArrayMap;
34 import android.view.textclassifier.TextClassification;
35 import android.view.textclassifier.TextClassificationManager;
36 import android.view.textclassifier.TextClassifier;
37 import android.view.textclassifier.TextLinks;
38 
39 import androidx.test.filters.SmallTest;
40 import androidx.test.runner.AndroidJUnit4;
41 
42 import com.android.systemui.R;
43 import com.android.systemui.SysuiTestCase;
44 
45 import com.google.android.collect.Lists;
46 
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.mockito.Answers;
51 import org.mockito.Mock;
52 import org.mockito.Mockito;
53 import org.mockito.MockitoAnnotations;
54 
55 import java.util.Map;
56 import java.util.Optional;
57 
58 @SmallTest
59 @RunWith(AndroidJUnit4.class)
60 public class ClipboardOverlayUtilsTest extends SysuiTestCase {
61 
62     private ClipboardOverlayUtils mClipboardUtils;
63     @Mock
64     private TextClassificationManager mTextClassificationManager;
65     @Mock
66     private TextClassifier mTextClassifier;
67 
68     @Mock
69     private ClipData.Item mClipDataItem;
70 
71     @Before
setUp()72     public void setUp() {
73         MockitoAnnotations.initMocks(this);
74 
75         when(mTextClassificationManager.getTextClassifier()).thenReturn(mTextClassifier);
76         mClipboardUtils = new ClipboardOverlayUtils(mTextClassificationManager);
77     }
78 
79     @Test
test_getAction_noLinks_returnsEmptyOptional()80     public void test_getAction_noLinks_returnsEmptyOptional() {
81         Optional<RemoteAction> action =
82                 mClipboardUtils.getAction(Mockito.mock(TextLinks.class), "abc");
83 
84         assertTrue(action.isEmpty());
85     }
86 
87     @Test
test_getAction_returnsFirstLink()88     public void test_getAction_returnsFirstLink() {
89         TextLinks links = getFakeTextLinksBuilder().build();
90         RemoteAction actionA = constructRemoteAction("abc");
91         RemoteAction actionB = constructRemoteAction("def");
92         TextClassification classificationA = Mockito.mock(TextClassification.class);
93         when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
94         TextClassification classificationB = Mockito.mock(TextClassification.class);
95         when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
96         when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
97                 classificationA, classificationB);
98 
99         RemoteAction result = mClipboardUtils.getAction(links, "test").orElse(null);
100 
101         assertEquals(actionA, result);
102     }
103 
104     @Test
test_getAction_skipsMatchingComponent()105     public void test_getAction_skipsMatchingComponent() {
106         TextLinks links = getFakeTextLinksBuilder().build();
107         RemoteAction actionA = constructRemoteAction("abc");
108         RemoteAction actionB = constructRemoteAction("def");
109         TextClassification classificationA = Mockito.mock(TextClassification.class);
110         when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
111         TextClassification classificationB = Mockito.mock(TextClassification.class);
112         when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
113         when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
114                 classificationA, classificationB);
115 
116         RemoteAction result = mClipboardUtils.getAction(links, "abc").orElse(null);
117 
118         assertEquals(actionB, result);
119     }
120 
121     @Test
test_getAction_skipsShortEntity()122     public void test_getAction_skipsShortEntity() {
123         TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
124         final Map<String, Float> scores = new ArrayMap<>();
125         scores.put(TextClassifier.TYPE_EMAIL, 1f);
126         textLinks.addLink(20, 22, scores);
127         textLinks.addLink(0, 22, scores);
128 
129         RemoteAction actionA = constructRemoteAction("abc");
130         RemoteAction actionB = constructRemoteAction("def");
131         TextClassification classificationA = Mockito.mock(TextClassification.class);
132         when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
133         TextClassification classificationB = Mockito.mock(TextClassification.class);
134         when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
135         when(mTextClassifier.classifyText(anyString(), eq(20), eq(22), isNull())).thenReturn(
136                 classificationA);
137         when(mTextClassifier.classifyText(anyString(), eq(0), eq(22), isNull())).thenReturn(
138                 classificationB);
139 
140         RemoteAction result = mClipboardUtils.getAction(textLinks.build(), "test").orElse(null);
141 
142         assertEquals(actionB, result);
143     }
144 
145     @Test
test_extra_withPackage_returnsTrue()146     public void test_extra_withPackage_returnsTrue() {
147         PersistableBundle b = new PersistableBundle();
148         b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true);
149         ClipData data = constructClipData(
150                 new String[]{"text/plain"}, new ClipData.Item("6175550000"), b);
151         TestableResources res = mContext.getOrCreateTestableResources();
152         res.addOverride(
153                 R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity");
154 
155         assertTrue(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote"));
156     }
157 
158     @Test
test_noExtra_returnsFalse()159     public void test_noExtra_returnsFalse() {
160         ClipData data = constructClipData(
161                 new String[]{"text/plain"}, new ClipData.Item("6175550000"), null);
162         TestableResources res = mContext.getOrCreateTestableResources();
163         res.addOverride(
164                 R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity");
165 
166         assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote"));
167     }
168 
169     @Test
test_falseExtra_returnsFalse()170     public void test_falseExtra_returnsFalse() {
171         PersistableBundle b = new PersistableBundle();
172         b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, false);
173         ClipData data = constructClipData(
174                 new String[]{"text/plain"}, new ClipData.Item("6175550000"), b);
175         TestableResources res = mContext.getOrCreateTestableResources();
176         res.addOverride(
177                 R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity");
178 
179         assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote"));
180     }
181 
182     @Test
test_wrongPackage_returnsFalse()183     public void test_wrongPackage_returnsFalse() {
184         PersistableBundle b = new PersistableBundle();
185         b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true);
186         ClipData data = constructClipData(
187                 new String[]{"text/plain"}, new ClipData.Item("6175550000"), b);
188 
189         assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, ""));
190     }
191 
constructClipData(String[] mimeTypes, ClipData.Item item, PersistableBundle extras)192     private static ClipData constructClipData(String[] mimeTypes, ClipData.Item item,
193             PersistableBundle extras) {
194         ClipDescription description = new ClipDescription("Test", mimeTypes);
195         if (extras != null) {
196             description.setExtras(extras);
197         }
198         return new ClipData(description, item);
199     }
200 
constructRemoteAction(String packageName)201     private static RemoteAction constructRemoteAction(String packageName) {
202         RemoteAction action = Mockito.mock(RemoteAction.class, Answers.RETURNS_DEEP_STUBS);
203         when(action.getActionIntent().getIntent().getComponent().getPackageName())
204                 .thenReturn(packageName);
205         return action;
206     }
207 
getFakeTextLinksBuilder()208     private static TextLinks.Builder getFakeTextLinksBuilder() {
209         TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
210         final Map<String, Float> scores = new ArrayMap<>();
211         scores.put(TextClassifier.TYPE_EMAIL, 1f);
212         textLinks.addLink(0, 22, scores);
213         textLinks.addLink(0, 22, scores);
214         return textLinks;
215     }
216 }
217