• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.settings.intelligence.search.query;
18 
19 import static com.android.settings.intelligence.search.sitemap.HighlightableMenu.MENU_KEY_SYSTEM;
20 
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ServiceInfo;
26 import android.icu.text.ListFormatter;
27 import androidx.annotation.NonNull;
28 import androidx.annotation.VisibleForTesting;
29 import android.util.Log;
30 import android.view.InputDevice;
31 import android.view.inputmethod.InputMethodInfo;
32 import android.view.inputmethod.InputMethodManager;
33 import android.view.inputmethod.InputMethodSubtype;
34 
35 import com.android.settings.intelligence.R;
36 import com.android.settings.intelligence.nano.SettingsIntelligenceLogProto;
37 import com.android.settings.intelligence.search.ResultPayload;
38 import com.android.settings.intelligence.search.SearchFeatureProvider;
39 import com.android.settings.intelligence.search.SearchResult;
40 import com.android.settings.intelligence.search.indexing.DatabaseIndexingUtils;
41 import com.android.settings.intelligence.search.sitemap.SiteMapManager;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Locale;
48 import java.util.Set;
49 
50 public class InputDeviceResultTask extends SearchQueryTask.QueryWorker {
51 
52     private static final String TAG = "InputResultFutureTask";
53 
54     public static final int QUERY_WORKER_ID =
55             SettingsIntelligenceLogProto.SettingsIntelligenceEvent.SEARCH_QUERY_INPUT_DEVICES;
56 
57     @VisibleForTesting
58     static final String PHYSICAL_KEYBOARD_FRAGMENT =
59             "com.android.settings.inputmethod.PhysicalKeyboardFragment";
60     @VisibleForTesting
61     static final String VIRTUAL_KEYBOARD_FRAGMENT =
62             "com.android.settings.inputmethod.AvailableVirtualKeyboardFragment";
63 
newTask(Context context, SiteMapManager manager, String query)64     public static SearchQueryTask newTask(Context context, SiteMapManager manager,
65             String query) {
66         return new SearchQueryTask(new InputDeviceResultTask(context, manager, query));
67     }
68 
69 
70     private static final int NAME_NO_MATCH = -1;
71 
72     private final InputMethodManager mImm;
73     private final PackageManager mPackageManager;
74 
75     private List<String> mPhysicalKeyboardBreadcrumb;
76     private List<String> mVirtualKeyboardBreadcrumb;
77 
InputDeviceResultTask(Context context, SiteMapManager manager, String query)78     public InputDeviceResultTask(Context context, SiteMapManager manager, String query) {
79         super(context, manager, query);
80 
81         mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
82         mPackageManager = context.getPackageManager();
83     }
84 
85     @Override
getQueryWorkerId()86     protected int getQueryWorkerId() {
87         return QUERY_WORKER_ID;
88     }
89 
90     @Override
query()91     protected List<? extends SearchResult> query() {
92         long startTime = System.currentTimeMillis();
93         final List<SearchResult> results = new ArrayList<>();
94         results.addAll(buildPhysicalKeyboardSearchResults());
95         results.addAll(buildVirtualKeyboardSearchResults());
96         Collections.sort(results);
97         if (SearchFeatureProvider.DEBUG) {
98             Log.d(TAG, "Input search loading took:" + (System.currentTimeMillis() - startTime));
99         }
100         return results;
101     }
102 
buildPhysicalKeyboardSearchResults()103     private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
104         final Set<SearchResult> results = new HashSet<>();
105         final String screenTitle = mContext.getString(R.string.physical_keyboard_title);
106 
107         for (final InputDevice device : getPhysicalFullKeyboards()) {
108             final String deviceName = device.getName();
109             final int wordDiff = SearchQueryUtils.getWordDifference(deviceName, mQuery);
110             if (wordDiff == NAME_NO_MATCH) {
111                 continue;
112             }
113             final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(
114                     PHYSICAL_KEYBOARD_FRAGMENT, deviceName, screenTitle, MENU_KEY_SYSTEM);
115             results.add(new SearchResult.Builder()
116                     .setTitle(deviceName)
117                     .setPayload(new ResultPayload(intent))
118                     .setDataKey(deviceName)
119                     .setRank(wordDiff)
120                     .addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
121                     .build());
122         }
123         return results;
124     }
125 
buildVirtualKeyboardSearchResults()126     private Set<SearchResult> buildVirtualKeyboardSearchResults() {
127         final Set<SearchResult> results = new HashSet<>();
128         final String screenTitle = mContext.getString(R.string.add_virtual_keyboard);
129         final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
130         for (InputMethodInfo info : inputMethods) {
131             final String title = info.loadLabel(mPackageManager).toString();
132             final String summary = getSubtypeLocaleNameListAsSentence(
133                     getAllSubtypesOf(info), mContext, info);
134             int wordDiff = SearchQueryUtils.getWordDifference(title, mQuery);
135             if (wordDiff == NAME_NO_MATCH) {
136                 wordDiff = SearchQueryUtils.getWordDifference(summary, mQuery);
137             }
138             if (wordDiff == NAME_NO_MATCH) {
139                 continue;
140             }
141             final ServiceInfo serviceInfo = info.getServiceInfo();
142             final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
143                     .flattenToString();
144             final Intent intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(
145                     VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle, MENU_KEY_SYSTEM);
146             results.add(new SearchResult.Builder()
147                     .setTitle(title)
148                     .setSummary(summary)
149                     .setRank(wordDiff)
150                     .setDataKey(key)
151                     .addBreadcrumbs(getVirtualKeyboardBreadCrumb())
152                     .setPayload(new ResultPayload(intent))
153                     .build());
154         }
155         return results;
156     }
157 
getPhysicalKeyboardBreadCrumb()158     private List<String> getPhysicalKeyboardBreadCrumb() {
159         if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
160             mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
161                     mContext, PHYSICAL_KEYBOARD_FRAGMENT,
162                     mContext.getString(R.string.physical_keyboard_title));
163         }
164         return mPhysicalKeyboardBreadcrumb;
165     }
166 
167 
getVirtualKeyboardBreadCrumb()168     private List<String> getVirtualKeyboardBreadCrumb() {
169         if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
170             final Context context = mContext;
171             mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
172                     context, VIRTUAL_KEYBOARD_FRAGMENT,
173                     context.getString(R.string.add_virtual_keyboard));
174         }
175         return mVirtualKeyboardBreadcrumb;
176     }
177 
getPhysicalFullKeyboards()178     private List<InputDevice> getPhysicalFullKeyboards() {
179         final List<InputDevice> keyboards = new ArrayList<>();
180         final int[] deviceIds = InputDevice.getDeviceIds();
181         if (deviceIds != null) {
182             for (int deviceId : deviceIds) {
183                 final InputDevice device = InputDevice.getDevice(deviceId);
184                 if (isFullPhysicalKeyboard(device)) {
185                     keyboards.add(device);
186                 }
187             }
188         }
189         return keyboards;
190     }
191 
192     @NonNull
getSubtypeLocaleNameListAsSentence( @onNull final List<InputMethodSubtype> subtypes, @NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo)193     private static String getSubtypeLocaleNameListAsSentence(
194             @NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
195             @NonNull final InputMethodInfo inputMethodInfo) {
196         if (subtypes.isEmpty()) {
197             return "";
198         }
199         final Locale locale = Locale.getDefault();
200         final int subtypeCount = subtypes.size();
201         final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
202         for (int i = 0; i < subtypeCount; i++) {
203             subtypeNames[i] = subtypes.get(i).getDisplayName(context,
204                     inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
205                             .applicationInfo);
206         }
207         return toSentenceCase(
208                 ListFormatter.getInstance(locale).format((Object[]) subtypeNames), locale);
209     }
210 
toSentenceCase(String str, Locale locale)211     private static String toSentenceCase(String str, Locale locale) {
212         if (str.isEmpty()) {
213             return str;
214         }
215         final int firstCodePointLen = str.offsetByCodePoints(0, 1);
216         return str.substring(0, firstCodePointLen).toUpperCase(locale)
217                 + str.substring(firstCodePointLen);
218     }
219 
isFullPhysicalKeyboard(InputDevice device)220     private static boolean isFullPhysicalKeyboard(InputDevice device) {
221         return device != null && !device.isVirtual() &&
222                 (device.getSources() & InputDevice.SOURCE_KEYBOARD)
223                         == InputDevice.SOURCE_KEYBOARD
224                 && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC;
225     }
226 
getAllSubtypesOf(final InputMethodInfo imi)227     private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
228         final int subtypeCount = imi.getSubtypeCount();
229         final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
230         for (int index = 0; index < subtypeCount; index++) {
231             allSubtypes.add(imi.getSubtypeAt(index));
232         }
233         return allSubtypes;
234     }
235 }
236