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.server.inputmethod;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import static org.junit.Assert.assertEquals;
22 
23 import android.content.ComponentName;
24 import android.content.Intent;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.util.ArrayMap;
29 import android.view.inputmethod.InputMethod;
30 import android.view.inputmethod.InputMethodInfo;
31 
32 import androidx.test.ext.junit.runners.AndroidJUnit4;
33 
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 @RunWith(AndroidJUnit4.class)
41 public class InputMethodManagerServiceRestrictImeAmountTest extends
42         InputMethodManagerServiceTestBase {
43 
44     @Test
testFilterInputMethodServices_loadsAllImesBelowThreshold()45     public void testFilterInputMethodServices_loadsAllImesBelowThreshold() {
46         List<ResolveInfo> resolveInfoList = new ArrayList<>();
47         for (int i = 0; i < 5; i++) {
48             resolveInfoList.add(
49                     createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
50         }
51 
52         final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
53                 List.of());
54         assertEquals(5, methodList.size());
55     }
56 
57     @Test
testFilterInputMethodServices_ignoresImesBeyondThreshold()58     public void testFilterInputMethodServices_ignoresImesBeyondThreshold() {
59         List<ResolveInfo> resolveInfoList = new ArrayList<>();
60         for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
61             resolveInfoList.add(
62                     createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
63         }
64 
65         final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
66                 List.of());
67         assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
68                 InputMethodInfo.MAX_IMES_PER_PACKAGE);
69     }
70 
71     @Test
testFilterInputMethodServices_loadsSystemImesBeyondThreshold()72     public void testFilterInputMethodServices_loadsSystemImesBeyondThreshold() {
73         List<ResolveInfo> resolveInfoList = new ArrayList<>();
74         for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
75             resolveInfoList.add(
76                     createFakeSystemResolveInfo("com.android.apps.inputmethod.systemime",
77                             "SystemIME" + i));
78         }
79 
80         final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
81                 List.of());
82         assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
83                 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE);
84     }
85 
86     @Test
testFilterInputMethodServices_ignoresImesBeyondThresholdFromTwoPackages()87     public void testFilterInputMethodServices_ignoresImesBeyondThresholdFromTwoPackages() {
88         List<ResolveInfo> resolveInfoList = new ArrayList<>();
89         for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
90             resolveInfoList.add(
91                     createFakeResolveInfo("com.android.apps.inputmethod.simpleime1", "IME1_" + i));
92         }
93         for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
94             resolveInfoList.add(
95                     createFakeResolveInfo("com.android.apps.inputmethod.simpleime2", "IME2_" + i));
96         }
97 
98         final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
99                 List.of());
100         assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
101                 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE);
102     }
103 
104     @Test
testFilterInputMethodServices_stillLoadsEnabledImesBeyondThreshold()105     public void testFilterInputMethodServices_stillLoadsEnabledImesBeyondThreshold() {
106         final ResolveInfo enabledIme = createFakeResolveInfo(
107                 "com.android.apps.inputmethod.simpleime_enabled", "EnabledIME");
108 
109         List<ResolveInfo> resolveInfoList = new ArrayList<>();
110         for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
111             resolveInfoList.add(
112                     createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
113         }
114         resolveInfoList.add(enabledIme);
115 
116         final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
117                 List.of(new ComponentName(enabledIme.serviceInfo.packageName,
118                         enabledIme.serviceInfo.name).flattenToShortString()));
119 
120         assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
121                 1 + InputMethodInfo.MAX_IMES_PER_PACKAGE);
122     }
123 
filterInputMethodServices(List<ResolveInfo> resolveInfoList, List<String> enabledComponents)124     private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
125             List<String> enabledComponents) {
126         final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
127         final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
128         InputMethodManagerService.filterInputMethodServices(new ArrayMap<>(), methodMap, methodList,
129                 enabledComponents, mContext, resolveInfoList);
130         return methodList;
131     }
132 
createFakeSystemResolveInfo(String packageName, String componentName)133     private ResolveInfo createFakeSystemResolveInfo(String packageName, String componentName) {
134         final ResolveInfo ime = createFakeResolveInfo(packageName, componentName);
135         ime.serviceInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
136         return ime;
137     }
138 
createFakeResolveInfo(String packageName, String componentName)139     private ResolveInfo createFakeResolveInfo(String packageName, String componentName) {
140         final ResolveInfo ime = getResolveInfo("com.android.apps.inputmethod.simpleime");
141         if (packageName != null) {
142             ime.serviceInfo.packageName = packageName;
143         }
144         if (componentName != null) {
145             ime.serviceInfo.name = componentName;
146         }
147         return ime;
148     }
149 
getResolveInfo(String packageName)150     private ResolveInfo getResolveInfo(String packageName) {
151         final int flags = PackageManager.GET_META_DATA
152                 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
153         final List<ResolveInfo> ime = mContext.getPackageManager().queryIntentServices(
154                 new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName),
155                 PackageManager.ResolveInfoFlags.of(flags));
156         assertWithMessage("Loaded IMEs").that(ime.size()).isGreaterThan(0);
157         return ime.get(0);
158     }
159 }
160