1e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang/*
2e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * Copyright (C) 2017 The Android Open Source Project
3e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang *
4e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * Licensed under the Apache License, Version 2.0 (the "License");
5e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * you may not use this file except in compliance with the License.
6e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * You may obtain a copy of the License at
7e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang *
8e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang *      http://www.apache.org/licenses/LICENSE-2.0
9e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang *
10e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * Unless required by applicable law or agreed to in writing, software
11e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * distributed under the License is distributed on an "AS IS" BASIS,
12e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * See the License for the specific language governing permissions and
14e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * limitations under the License.
15e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang */
16e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
17e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangpackage com.android.settings.search;
18e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
19e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport static android.content.Context.INPUT_METHOD_SERVICE;
20e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
21e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.content.ComponentName;
22e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.content.Context;
23e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.content.Intent;
24e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.content.pm.PackageManager;
25e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.content.pm.ServiceInfo;
26e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.hardware.input.InputManager;
27e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.hardware.input.KeyboardLayout;
28e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.support.annotation.VisibleForTesting;
29e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.view.InputDevice;
30e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.view.inputmethod.InputMethodInfo;
31e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.view.inputmethod.InputMethodManager;
32e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport android.view.inputmethod.InputMethodSubtype;
33e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
34e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport com.android.settings.R;
35e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport com.android.settings.dashboard.SiteMapManager;
36e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
37e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport com.android.settings.inputmethod.PhysicalKeyboardFragment;
38e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport com.android.settings.utils.AsyncLoader;
39e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
40e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
41e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport java.util.ArrayList;
42e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport java.util.HashSet;
43e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport java.util.List;
44e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport java.util.Objects;
45e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangimport java.util.Set;
46e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
47e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang/**
48e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang * Search result for input devices (physical/virtual keyboard, game controllers, etc)
49e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang */
50e3535d9998363cbf352b5f8feb9277475e380944Fan Zhangpublic class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
51e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private static final int NAME_NO_MATCH = -1;
52e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
53e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    @VisibleForTesting
54e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
55e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    @VisibleForTesting
56e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    static final String VIRTUAL_KEYBOARD_FRAGMENT =
57e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            AvailableVirtualKeyboardFragment.class.getName();
58e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
59e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private final SiteMapManager mSiteMapManager;
60e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private final InputManager mInputManager;
61e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private final InputMethodManager mImm;
62e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private final PackageManager mPackageManager;
63e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    @VisibleForTesting
64e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    final String mQuery;
65e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
66e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private List<String> mPhysicalKeyboardBreadcrumb;
67e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private List<String> mVirtualKeyboardBreadcrumb;
68e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
69e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    public InputDeviceResultLoader(Context context, String query, SiteMapManager mapManager) {
70e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        super(context);
71e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        mQuery = query;
72e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        mSiteMapManager = mapManager;
73e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
74e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
75e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        mPackageManager = context.getPackageManager();
76e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
77e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
78e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    @Override
79e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    protected void onDiscardResult(Set<? extends SearchResult> result) {
80e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
81e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
82e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    @Override
83e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    public Set<? extends SearchResult> loadInBackground() {
84e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final Set<SearchResult> results = new HashSet<>();
85e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        results.addAll(buildPhysicalKeyboardSearchResults());
86e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        results.addAll(buildVirtualKeyboardSearchResults());
87e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        return results;
88e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
89e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
90e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
91e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final Set<SearchResult> results = new HashSet<>();
92e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final Context context = getContext();
93e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final String screenTitle = context.getString(R.string.physical_keyboard_title);
94e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
95e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        for (final InputDevice device : getPhysicalFullKeyboards()) {
96e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final String deviceName = device.getName();
97e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName, mQuery);
98e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            if (wordDiff == NAME_NO_MATCH) {
99e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                continue;
100e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            }
101e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final String keyboardLayoutDescriptor = mInputManager
102e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
103e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
104e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    ? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
105e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final String summary = (keyboardLayout != null)
106e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    ? keyboardLayout.toString()
107e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    : context.getString(R.string.keyboard_layout_default_label);
108e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final String key = deviceName;
109e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
110e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(context,
111e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    PHYSICAL_KEYBOARD_FRAGMENT, key, screenTitle);
112e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            results.add(new SearchResult.Builder()
113e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setTitle(deviceName)
114e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setPayload(new ResultPayload(intent))
115e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, key))
116e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setSummary(summary)
117e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setRank(wordDiff)
118e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
119e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .build());
120e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        }
121e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        return results;
122e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
123e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
124e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private Set<SearchResult> buildVirtualKeyboardSearchResults() {
125e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final Set<SearchResult> results = new HashSet<>();
126e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final Context context = getContext();
127e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final String screenTitle = context.getString(R.string.add_virtual_keyboard);
128e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
129e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        for (InputMethodInfo info : inputMethods) {
130e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final String title = info.loadLabel(mPackageManager).toString();
131e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final String summary = InputMethodAndSubtypeUtil
132e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), context, info);
133e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
134e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            if (wordDiff == NAME_NO_MATCH) {
135e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
136e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            }
137e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            if (wordDiff == NAME_NO_MATCH) {
138e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                continue;
139e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            }
140e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final ServiceInfo serviceInfo = info.getServiceInfo();
141e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
142e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .flattenToString();
143e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(context,
144e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
145e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            results.add(new SearchResult.Builder()
146e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setTitle(title)
147e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setSummary(summary)
148e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setRank(wordDiff)
149e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key))
150e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .addBreadcrumbs(getVirtualKeyboardBreadCrumb())
151e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .setPayload(new ResultPayload(intent))
152e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    .build());
153e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        }
154e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        return results;
155e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
156e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
157e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private List<String> getPhysicalKeyboardBreadCrumb() {
158e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
159e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final Context context = getContext();
160e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
161e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    context, PHYSICAL_KEYBOARD_FRAGMENT,
162e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    context.getString(R.string.physical_keyboard_title));
163e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        }
164e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        return mPhysicalKeyboardBreadcrumb;
165e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
166e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
167e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
168e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private List<String> getVirtualKeyboardBreadCrumb() {
169e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
170e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            final Context context = getContext();
171e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
172e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    context, VIRTUAL_KEYBOARD_FRAGMENT,
173e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    context.getString(R.string.add_virtual_keyboard));
174e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        }
175e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        return mVirtualKeyboardBreadcrumb;
176e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
177e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
178e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private List<InputDevice> getPhysicalFullKeyboards() {
179e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final List<InputDevice> keyboards = new ArrayList<>();
180e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final int[] deviceIds = InputDevice.getDeviceIds();
181e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        if (deviceIds != null) {
182e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            for (int deviceId : deviceIds) {
183e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                final InputDevice device = InputDevice.getDevice(deviceId);
184e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
185e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                    keyboards.add(device);
186e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang                }
187e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            }
188e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        }
189e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        return keyboards;
190e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
191e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang
192e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
193e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final int subtypeCount = imi.getSubtypeCount();
194e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
195e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        for (int index = 0; index < subtypeCount; index++) {
196e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang            allSubtypes.add(imi.getSubtypeAt(index));
197e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        }
198e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang        return allSubtypes;
199e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang    }
200e3535d9998363cbf352b5f8feb9277475e380944Fan Zhang}
201