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.search; 18 19 import static android.content.Context.INPUT_METHOD_SERVICE; 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.hardware.input.InputManager; 27 import android.hardware.input.KeyboardLayout; 28 import android.support.annotation.VisibleForTesting; 29 import android.view.InputDevice; 30 import android.view.inputmethod.InputMethodInfo; 31 import android.view.inputmethod.InputMethodManager; 32 import android.view.inputmethod.InputMethodSubtype; 33 34 import com.android.settings.R; 35 import com.android.settings.dashboard.SiteMapManager; 36 import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment; 37 import com.android.settings.inputmethod.PhysicalKeyboardFragment; 38 import com.android.settings.utils.AsyncLoader; 39 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil; 40 41 import java.util.ArrayList; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.Set; 46 47 /** 48 * Search result for input devices (physical/virtual keyboard, game controllers, etc) 49 */ 50 public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchResult>> { 51 private static final int NAME_NO_MATCH = -1; 52 53 @VisibleForTesting 54 static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName(); 55 @VisibleForTesting 56 static final String VIRTUAL_KEYBOARD_FRAGMENT = 57 AvailableVirtualKeyboardFragment.class.getName(); 58 59 private final SiteMapManager mSiteMapManager; 60 private final InputManager mInputManager; 61 private final InputMethodManager mImm; 62 private final PackageManager mPackageManager; 63 @VisibleForTesting 64 final String mQuery; 65 66 private List<String> mPhysicalKeyboardBreadcrumb; 67 private List<String> mVirtualKeyboardBreadcrumb; 68 69 public InputDeviceResultLoader(Context context, String query, SiteMapManager mapManager) { 70 super(context); 71 mQuery = query; 72 mSiteMapManager = mapManager; 73 mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); 74 mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE); 75 mPackageManager = context.getPackageManager(); 76 } 77 78 @Override 79 protected void onDiscardResult(Set<? extends SearchResult> result) { 80 } 81 82 @Override 83 public Set<? extends SearchResult> loadInBackground() { 84 final Set<SearchResult> results = new HashSet<>(); 85 results.addAll(buildPhysicalKeyboardSearchResults()); 86 results.addAll(buildVirtualKeyboardSearchResults()); 87 return results; 88 } 89 90 private Set<SearchResult> buildPhysicalKeyboardSearchResults() { 91 final Set<SearchResult> results = new HashSet<>(); 92 final Context context = getContext(); 93 final String screenTitle = context.getString(R.string.physical_keyboard_title); 94 95 for (final InputDevice device : getPhysicalFullKeyboards()) { 96 final String deviceName = device.getName(); 97 final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName, mQuery); 98 if (wordDiff == NAME_NO_MATCH) { 99 continue; 100 } 101 final String keyboardLayoutDescriptor = mInputManager 102 .getCurrentKeyboardLayoutForInputDevice(device.getIdentifier()); 103 final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null) 104 ? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null; 105 final String summary = (keyboardLayout != null) 106 ? keyboardLayout.toString() 107 : context.getString(R.string.keyboard_layout_default_label); 108 final String key = deviceName; 109 110 final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(context, 111 PHYSICAL_KEYBOARD_FRAGMENT, key, screenTitle); 112 results.add(new SearchResult.Builder() 113 .setTitle(deviceName) 114 .setPayload(new ResultPayload(intent)) 115 .setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, key)) 116 .setSummary(summary) 117 .setRank(wordDiff) 118 .addBreadcrumbs(getPhysicalKeyboardBreadCrumb()) 119 .build()); 120 } 121 return results; 122 } 123 124 private Set<SearchResult> buildVirtualKeyboardSearchResults() { 125 final Set<SearchResult> results = new HashSet<>(); 126 final Context context = getContext(); 127 final String screenTitle = context.getString(R.string.add_virtual_keyboard); 128 final List<InputMethodInfo> inputMethods = mImm.getInputMethodList(); 129 for (InputMethodInfo info : inputMethods) { 130 final String title = info.loadLabel(mPackageManager).toString(); 131 final String summary = InputMethodAndSubtypeUtil 132 .getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), context, info); 133 int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery); 134 if (wordDiff == NAME_NO_MATCH) { 135 wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery); 136 } 137 if (wordDiff == NAME_NO_MATCH) { 138 continue; 139 } 140 final ServiceInfo serviceInfo = info.getServiceInfo(); 141 final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name) 142 .flattenToString(); 143 final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(context, 144 VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle); 145 results.add(new SearchResult.Builder() 146 .setTitle(title) 147 .setSummary(summary) 148 .setRank(wordDiff) 149 .setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key)) 150 .addBreadcrumbs(getVirtualKeyboardBreadCrumb()) 151 .setPayload(new ResultPayload(intent)) 152 .build()); 153 } 154 return results; 155 } 156 157 private List<String> getPhysicalKeyboardBreadCrumb() { 158 if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) { 159 final Context context = getContext(); 160 mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb( 161 context, PHYSICAL_KEYBOARD_FRAGMENT, 162 context.getString(R.string.physical_keyboard_title)); 163 } 164 return mPhysicalKeyboardBreadcrumb; 165 } 166 167 168 private List<String> getVirtualKeyboardBreadCrumb() { 169 if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) { 170 final Context context = getContext(); 171 mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb( 172 context, VIRTUAL_KEYBOARD_FRAGMENT, 173 context.getString(R.string.add_virtual_keyboard)); 174 } 175 return mVirtualKeyboardBreadcrumb; 176 } 177 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 (device != null && !device.isVirtual() && device.isFullKeyboard()) { 185 keyboards.add(device); 186 } 187 } 188 } 189 return keyboards; 190 } 191 192 private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) { 193 final int subtypeCount = imi.getSubtypeCount(); 194 final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount); 195 for (int index = 0; index < subtypeCount; index++) { 196 allSubtypes.add(imi.getSubtypeAt(index)); 197 } 198 return allSubtypes; 199 } 200 } 201