Merge "Delete DynamicindexableContentMonitor & all refs to it" into oc-mr1-dev

This commit is contained in:
TreeHugger Robot
2017-08-10 00:28:03 +00:00
committed by Android (Google) Code Review
30 changed files with 597 additions and 1209 deletions

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="available_virtual_keyboard_page"
android:title="@string/add_virtual_keyboard">
</PreferenceScreen>

View File

@@ -59,7 +59,6 @@ import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.dashboard.DashboardSummary;
import com.android.settings.development.DevelopmentSettings;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.DynamicIndexableContentMonitor;
import com.android.settings.search.SearchActivity;
import com.android.settings.wfd.WifiDisplaySettings;
import com.android.settings.widget.SwitchBar;
@@ -76,8 +75,6 @@ public class SettingsActivity extends SettingsDrawerActivity
private static final String LOG_TAG = "Settings";
public static final int LOADER_ID_INDEXABLE_CONTENT_MONITOR = 1;
// Constants for state save/restore
private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
@VisibleForTesting
@@ -183,8 +180,6 @@ public class SettingsActivity extends SettingsDrawerActivity
}
};
private DynamicIndexableContentMonitor mDynamicIndexableContentMonitor;
private SwitchBar mSwitchBar;
private Button mNextButton;
@@ -535,10 +530,6 @@ public class SettingsActivity extends SettingsDrawerActivity
mDevelopmentPreferencesListener);
registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (mDynamicIndexableContentMonitor == null) {
mDynamicIndexableContentMonitor = new DynamicIndexableContentMonitor();
}
mDynamicIndexableContentMonitor.register(this, LOADER_ID_INDEXABLE_CONTENT_MONITOR);
updateTilesList();
}
@@ -550,9 +541,6 @@ public class SettingsActivity extends SettingsDrawerActivity
mDevelopmentPreferencesListener);
mDevelopmentPreferencesListener = null;
unregisterReceiver(mBatteryInfoReceiver);
if (mDynamicIndexableContentMonitor != null) {
mDynamicIndexableContentMonitor.unregister(this, LOADER_ID_INDEXABLE_CONTENT_MONITOR);
}
}
@Override

View File

@@ -21,7 +21,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -31,17 +30,15 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.preference.PreferenceScreen;
import android.provider.SearchIndexableResource;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
import com.android.settingslib.inputmethod.InputMethodPreference;
import com.android.settingslib.inputmethod.InputMethodSettingValuesWrapper;
@@ -60,10 +57,9 @@ public final class AvailableVirtualKeyboardFragment extends SettingsPreferenceFr
@Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.available_virtual_keyboard);
Activity activity = getActivity();
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity);
screen.setTitle(activity.getString(R.string.available_virtual_keyboard_category));
setPreferenceScreen(screen);
mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
mImm = activity.getSystemService(InputMethodManager.class);
mDpm = activity.getSystemService(DevicePolicyManager.class);
@@ -105,7 +101,7 @@ public final class AvailableVirtualKeyboardFragment extends SettingsPreferenceFr
}
try {
return packageManager.getDrawable(packageName, resId, applicationInfo);
} catch (Exception e){
} catch (Exception e) {
return null;
}
}
@@ -172,48 +168,16 @@ public final class AvailableVirtualKeyboardFragment extends SettingsPreferenceFr
}
}
private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
final int subtypeCount = imi.getSubtypeCount();
final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
for (int index = 0; index < subtypeCount; index++) {
allSubtypes.add(imi.getSubtypeAt(index));
}
return allSubtypes;
}
static List<SearchIndexableRaw> buildSearchIndexOfInputMethods(final Context context,
final List<InputMethodInfo> inputMethods, final String screenTitle) {
final List<SearchIndexableRaw> indexes = new ArrayList<>();
for (int i = 0; i < inputMethods.size(); i++) {
final InputMethodInfo imi = inputMethods.get(i);
final ServiceInfo serviceInfo = imi.getServiceInfo();
final SearchIndexableRaw index = new SearchIndexableRaw(context);
index.key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
.flattenToString();
index.title = imi.loadLabel(context.getPackageManager()).toString();
index.summaryOn = index.summaryOff = InputMethodAndSubtypeUtil
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(imi), context, imi);
index.screenTitle = screenTitle;
indexes.add(index);
}
return indexes;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
final List<InputMethodInfo> enabledInputMethods = imm.getEnabledInputMethodList();
final List<InputMethodInfo> disabledInputMethods = new ArrayList<>();
for (final InputMethodInfo imi : imm.getInputMethodList()) {
if (!enabledInputMethods.contains(imi)) {
disabledInputMethods.add(imi);
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
List<SearchIndexableResource> res = new ArrayList<>();
SearchIndexableResource index = new SearchIndexableResource(context);
index.xmlResId = R.xml.available_virtual_keyboard;
res.add(index);
return res;
}
}
final String screenTitle = context.getString(
R.string.available_virtual_keyboard_category);
return buildSearchIndexOfInputMethods(context, disabledInputMethods, screenTitle);
}
};
};
}

View File

@@ -31,6 +31,7 @@ import android.hardware.input.KeyboardLayout;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.SearchIndexableResource;
import android.provider.Settings.Secure;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
@@ -51,11 +52,11 @@ import com.android.settings.Settings;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -288,6 +289,7 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
final PhysicalKeyboardFragment mPhysicalKeyboardFragment;
@NonNull
final List<HardKeyboardDeviceInfo> mHardKeyboards;
public Callbacks(
@NonNull Context context,
@NonNull PhysicalKeyboardFragment physicalKeyboardFragment,
@@ -532,43 +534,14 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
}
}
public static List<InputDevice> getPhysicalFullKeyboards() {
List<InputDevice> keyboards = null;
for (final int deviceId : InputDevice.getDeviceIds()) {
final InputDevice device = InputDevice.getDevice(deviceId);
if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
if (keyboards == null) keyboards = new ArrayList<>();
keyboards.add(device);
}
}
return (keyboards == null) ? Collections.emptyList() : keyboards;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
final InputManager inputManager = (InputManager) context.getSystemService(
Context.INPUT_SERVICE);
final String screenTitle = context.getString(R.string.physical_keyboard_title);
final List<SearchIndexableRaw> indexes = new ArrayList<>();
for (final InputDevice device : getPhysicalFullKeyboards()) {
final String keyboardLayoutDescriptor = inputManager
.getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
? inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
final String summary = (keyboardLayout != null)
? keyboardLayout.toString()
: context.getString(R.string.keyboard_layout_default_label);
final SearchIndexableRaw index = new SearchIndexableRaw(context);
index.key = device.getName();
index.title = device.getName();
index.summaryOn = summary;
index.summaryOff = summary;
index.screenTitle = screenTitle;
indexes.add(index);
}
return indexes;
}
};
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.physical_keyboard_settings;
return Arrays.asList(sir);
}
};
}

View File

@@ -23,6 +23,7 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.support.v7.preference.Preference;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -33,14 +34,12 @@ import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
import com.android.settingslib.inputmethod.InputMethodPreference;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Arrays;
import java.util.List;
public final class VirtualKeyboardFragment extends SettingsPreferenceFragment implements Indexable {
@@ -121,13 +120,19 @@ public final class VirtualKeyboardFragment extends SettingsPreferenceFragment im
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
final List<InputMethodInfo> enabledInputMethods = imm.getEnabledInputMethodList();
final String screenTitle = context.getString(R.string.virtual_keyboard_category);
return AvailableVirtualKeyboardFragment
.buildSearchIndexOfInputMethods(context, enabledInputMethods, screenTitle);
}
};
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.virtual_keyboard_settings;
return Arrays.asList(sir);
}
@Override
public List<String> getNonIndexableKeys(Context context) {
final List<String> keys = super.getNonIndexableKeys(context);
keys.add("add_virtual_keyboard_screen");
return keys;
}
};
}

View File

@@ -52,6 +52,7 @@ public class LanguageAndInputSettings extends DashboardFragment {
private static final String TAG = "LangAndInputSettings";
private static final String KEY_TEXT_TO_SPEECH = "tts_settings_summary";
private static final String KEY_PHYSICAL_KEYBOARD = "physical_keyboard_pref";
@Override
public int getMetricsCategory() {
@@ -174,6 +175,7 @@ public class LanguageAndInputSettings extends DashboardFragment {
List<String> keys = super.getNonIndexableKeys(context);
// Duplicates in summary and details pages.
keys.add(KEY_TEXT_TO_SPEECH);
keys.add(KEY_PHYSICAL_KEYBOARD);
return keys;
}

View File

@@ -1,415 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.search;
import android.accessibilityservice.AccessibilityService;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.print.PrintManager;
import android.print.PrintServicesLoader;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.provider.UserDictionary;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.content.PackageMonitor;
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import com.android.settings.inputmethod.VirtualKeyboardFragment;
import com.android.settings.language.LanguageAndInputSettings;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.print.PrintSettingsFragment;
import java.util.ArrayList;
import java.util.List;
public final class DynamicIndexableContentMonitor implements
LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
// Shorten the class name because log TAG can be at most 23 chars.
private static final String TAG = "DynamicContentMonitor";
private static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000;
// A PackageMonitor shared among Settings activities.
private static final PackageChangeMonitor PACKAGE_CHANGE_MONITOR = new PackageChangeMonitor();
// Null if not initialized.
@Nullable private DatabaseIndexingManager mIndexManager;
private Context mContext;
private boolean mHasFeaturePrinting;
@VisibleForTesting
static Intent getAccessibilityServiceIntent(String packageName) {
final Intent intent = new Intent(AccessibilityService.SERVICE_INTERFACE);
intent.setPackage(packageName);
return intent;
}
@VisibleForTesting
static Intent getIMEServiceIntent(String packageName) {
final Intent intent = new Intent(InputMethod.SERVICE_INTERFACE);
intent.setPackage(packageName);
return intent;
}
@VisibleForTesting
static void resetForTesting() {
InputDevicesMonitor.getInstance().resetForTesting();
InputMethodServicesMonitor.getInstance().resetForTesting();
}
/**
* This instance holds a set of content monitor singleton objects.
*
* This object is created every time a sub-settings that extends {@code SettingsActivity}
* is created.
*/
public DynamicIndexableContentMonitor() {}
/**
* Creates and initializes a set of content monitor singleton objects if not yet exist.
* Also starts loading the list of print services.
* <code>mIndex</code> has non-null value after successfully initialized.
*
* @param activity used to get {@link LoaderManager}.
* @param loaderId id for loading print services.
*/
public void register(Activity activity, int loaderId) {
final boolean isUserUnlocked = activity
.getSystemService(UserManager.class)
.isUserUnlocked();
register(activity, loaderId, FeatureFactory.getFactory(activity)
.getSearchFeatureProvider().getIndexingManager(activity), isUserUnlocked);
}
/**
* For testing to inject {@link DatabaseIndexingManager} object.
* Also because currently Robolectric doesn't support API 24, we can not test code that calls
* {@link UserManager#isUserUnlocked()}.
*/
@VisibleForTesting
void register(Activity activity, int loaderId, DatabaseIndexingManager indexManager,
boolean isUserUnlocked) {
if (!isUserUnlocked) {
Log.w(TAG, "Skipping content monitoring because user is locked");
return;
}
final Context context = activity.getApplicationContext();
mContext = context;
mIndexManager = indexManager;
PACKAGE_CHANGE_MONITOR.registerMonitor(context);
mHasFeaturePrinting = context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_PRINTING);
if (mHasFeaturePrinting) {
activity.getLoaderManager().initLoader(loaderId, null /* args */, this /* callbacks */);
}
// Watch for input device changes.
InputDevicesMonitor.getInstance().initialize(context, mIndexManager);
// Start tracking packages.
InputMethodServicesMonitor.getInstance().initialize(context, mIndexManager);
}
/**
* Aborts loading the list of print services.
* Note that a set of content monitor singletons keep alive while Settings app is running.
*
* @param activity user to get {@link LoaderManager}.
* @param loaderId id for loading print services.
*/
public void unregister(Activity activity, int loaderId) {
if (mIndexManager == null) return;
PACKAGE_CHANGE_MONITOR.unregisterMonitor();
if (mHasFeaturePrinting) {
activity.getLoaderManager().destroyLoader(loaderId);
}
}
@Override
public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
return new PrintServicesLoader(
(PrintManager) mContext.getSystemService(Context.PRINT_SERVICE), mContext,
PrintManager.ALL_SERVICES);
}
@Override
public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
List<PrintServiceInfo> services) {
mIndexManager.updateFromClassNameResource(PrintSettingsFragment.class.getName(),
true /* includeInSearchResults */);
}
@Override
public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
// nothing to do
}
// A singleton that monitors input devices changes and updates indexes of physical keyboards.
private static class InputDevicesMonitor implements InputManager.InputDeviceListener {
// Null if not initialized.
@Nullable private DatabaseIndexingManager mIndexManager;
private InputManager mInputManager;
private InputDevicesMonitor() {}
private static class SingletonHolder {
private static final InputDevicesMonitor INSTANCE = new InputDevicesMonitor();
}
static InputDevicesMonitor getInstance() {
return SingletonHolder.INSTANCE;
}
@VisibleForTesting
synchronized void resetForTesting() {
if (mIndexManager != null) {
mInputManager.unregisterInputDeviceListener(this /* listener */);
}
mIndexManager = null;
}
synchronized void initialize(Context context, DatabaseIndexingManager indexManager) {
if (mIndexManager != null) return;
mIndexManager = indexManager;
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
buildIndex();
// Watch for input device changes.
mInputManager.registerInputDeviceListener(this /* listener */, null /* handler */);
}
private void buildIndex() {
mIndexManager.updateFromClassNameResource(PhysicalKeyboardFragment.class.getName(),
true /* includeInSearchResults */);
}
@Override
public void onInputDeviceAdded(int deviceId) {
buildIndex();
}
@Override
public void onInputDeviceRemoved(int deviceId) {
buildIndex();
}
@Override
public void onInputDeviceChanged(int deviceId) {
buildIndex();
}
}
// A singleton that monitors package installing, uninstalling, enabling, and disabling.
// Then updates indexes of accessibility services and input methods.
private static class PackageChangeMonitor extends PackageMonitor {
private static final String TAG = PackageChangeMonitor.class.getSimpleName();
// Null if not initialized. Guarded by {@link #mLock}.
@Nullable private PackageManager mPackageManager;
private final Object mLock = new Object();
public void registerMonitor(Context context) {
synchronized (mLock) {
if (mPackageManager != null) {
return;
}
mPackageManager = context.getPackageManager();
// Start tracking packages. Use background thread for monitoring. Note that no need
// to unregister this monitor. This should be alive while Settings app is running.
super.register(context, null /* thread */, UserHandle.CURRENT, false);
}
}
public void unregisterMonitor() {
synchronized (mLock) {
if (mPackageManager == null) {
return;
}
super.unregister();
mPackageManager = null;
}
}
// Covers installed, appeared external storage with the package, upgraded.
@Override
public void onPackageAppeared(String packageName, int reason) {
postPackageAvailable(packageName);
}
// Covers uninstalled, removed external storage with the package.
@Override
public void onPackageDisappeared(String packageName, int reason) {
postPackageUnavailable(packageName);
}
// Covers enabled, disabled.
@Override
public void onPackageModified(String packageName) {
try {
final int state = mPackageManager.getApplicationEnabledSetting(packageName);
if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
postPackageAvailable(packageName);
} else {
postPackageUnavailable(packageName);
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "Package does not exist: " + packageName, e);
}
}
private void postPackageAvailable(final String packageName) {
getRegisteredHandler().postDelayed(() -> {
InputMethodServicesMonitor.getInstance().onPackageAvailable(packageName);
}, DELAY_PROCESS_PACKAGE_CHANGE);
}
private void postPackageUnavailable(final String packageName) {
getRegisteredHandler().postDelayed(() -> {
InputMethodServicesMonitor.getInstance().onPackageUnavailable(packageName);
}, DELAY_PROCESS_PACKAGE_CHANGE);
}
}
// A singleton that holds list of available input methods and updates search index.
// Also it monitors user dictionary changes and updates search index.
private static class InputMethodServicesMonitor extends ContentObserver {
private static final Uri ENABLED_INPUT_METHODS_CONTENT_URI =
Settings.Secure.getUriFor(Settings.Secure.ENABLED_INPUT_METHODS);
// Null if not initialized.
@Nullable private DatabaseIndexingManager mIndexManager;
private PackageManager mPackageManager;
private ContentResolver mContentResolver;
private final List<String> mInputMethodServices = new ArrayList<>();
private InputMethodServicesMonitor() {
// No need for handler because {@link #onChange(boolean,Uri)} is short and quick.
super(null /* handler */);
}
private static class SingletonHolder {
private static final InputMethodServicesMonitor INSTANCE =
new InputMethodServicesMonitor();
}
static InputMethodServicesMonitor getInstance() {
return SingletonHolder.INSTANCE;
}
@VisibleForTesting
synchronized void resetForTesting() {
if (mIndexManager != null) {
mContentResolver.unregisterContentObserver(this /* observer */);
}
mIndexManager = null;
}
synchronized void initialize(Context context, DatabaseIndexingManager indexManager) {
final boolean hasFeatureIme = context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS);
if (!hasFeatureIme) return;
if (mIndexManager != null) return;
mIndexManager = indexManager;
mPackageManager = context.getPackageManager();
mContentResolver = context.getContentResolver();
mInputMethodServices.clear();
// Build index of {@link UserDictionary}.
buildIndex(LanguageAndInputSettings.class);
// Build index of IMEs.
buildIndex(VirtualKeyboardFragment.class);
buildIndex(AvailableVirtualKeyboardFragment.class);
// Cache IME service packages to know when they go away.
final InputMethodManager inputMethodManager = (InputMethodManager) context
.getSystemService(Context.INPUT_METHOD_SERVICE);
for (final InputMethodInfo inputMethod : inputMethodManager.getInputMethodList()) {
ServiceInfo serviceInfo = inputMethod.getServiceInfo();
if (serviceInfo != null) {
mInputMethodServices.add(serviceInfo.packageName);
}
}
// TODO: Implements by JobScheduler with TriggerContentUri parameters.
// Watch for related content URIs.
mContentResolver.registerContentObserver(UserDictionary.Words.CONTENT_URI,
true /* notifyForDescendants */, this /* observer */);
// Watch for changing enabled IMEs.
mContentResolver.registerContentObserver(ENABLED_INPUT_METHODS_CONTENT_URI,
false /* notifyForDescendants */, this /* observer */);
}
private void buildIndex(Class<?> indexClass) {
mIndexManager.updateFromClassNameResource(indexClass.getName(),
true /* includeInSearchResults */);
}
synchronized void onPackageAvailable(String packageName) {
if (mIndexManager == null) return;
if (mInputMethodServices.contains(packageName)) return;
final Intent intent = getIMEServiceIntent(packageName);
final List<ResolveInfo> services = mPackageManager
.queryIntentServices(intent, 0 /* flags */);
if (services == null || services.isEmpty()) return;
mInputMethodServices.add(packageName);
buildIndex(VirtualKeyboardFragment.class);
buildIndex(AvailableVirtualKeyboardFragment.class);
}
synchronized void onPackageUnavailable(String packageName) {
if (mIndexManager == null) return;
if (!mInputMethodServices.remove(packageName)) return;
buildIndex(VirtualKeyboardFragment.class);
buildIndex(AvailableVirtualKeyboardFragment.class);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (ENABLED_INPUT_METHODS_CONTENT_URI.equals(uri)) {
buildIndex(VirtualKeyboardFragment.class);
buildIndex(AvailableVirtualKeyboardFragment.class);
} else if (UserDictionary.Words.CONTENT_URI.equals(uri)) {
buildIndex(LanguageAndInputSettings.class);
}
}
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.search;
import static android.content.Context.INPUT_METHOD_SERVICE;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
import android.support.annotation.VisibleForTesting;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.settings.R;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import com.android.settings.utils.AsyncLoader;
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Search result for input devices (physical/virtual keyboard, game controllers, etc)
*/
public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
private static final int NAME_NO_MATCH = -1;
@VisibleForTesting
static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
@VisibleForTesting
static final String VIRTUAL_KEYBOARD_FRAGMENT =
AvailableVirtualKeyboardFragment.class.getName();
private final SiteMapManager mSiteMapManager;
private final InputManager mInputManager;
private final InputMethodManager mImm;
private final PackageManager mPackageManager;
@VisibleForTesting
final String mQuery;
private List<String> mPhysicalKeyboardBreadcrumb;
private List<String> mVirtualKeyboardBreadcrumb;
public InputDeviceResultLoader(Context context, String query, SiteMapManager mapManager) {
super(context);
mQuery = query;
mSiteMapManager = mapManager;
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
mPackageManager = context.getPackageManager();
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
@Override
public Set<? extends SearchResult> loadInBackground() {
final Set<SearchResult> results = new HashSet<>();
results.addAll(buildPhysicalKeyboardSearchResults());
results.addAll(buildVirtualKeyboardSearchResults());
return results;
}
private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
final Set<SearchResult> results = new HashSet<>();
final Context context = getContext();
final String screenTitle = context.getString(R.string.physical_keyboard_title);
for (final InputDevice device : getPhysicalFullKeyboards()) {
final String deviceName = device.getName();
final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName, mQuery);
if (wordDiff == NAME_NO_MATCH) {
continue;
}
final String keyboardLayoutDescriptor = mInputManager
.getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
final String summary = (keyboardLayout != null)
? keyboardLayout.toString()
: context.getString(R.string.keyboard_layout_default_label);
final String key = deviceName;
final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(context,
PHYSICAL_KEYBOARD_FRAGMENT, key, screenTitle);
results.add(new SearchResult.Builder()
.setTitle(deviceName)
.setPayload(new ResultPayload(intent))
.setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, key))
.setSummary(summary)
.setRank(wordDiff)
.addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
.build());
}
return results;
}
private Set<SearchResult> buildVirtualKeyboardSearchResults() {
final Set<SearchResult> results = new HashSet<>();
final Context context = getContext();
final String screenTitle = context.getString(R.string.add_virtual_keyboard);
final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
for (InputMethodInfo info : inputMethods) {
final String title = info.loadLabel(mPackageManager).toString();
final String summary = InputMethodAndSubtypeUtil
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), context, info);
int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
if (wordDiff == NAME_NO_MATCH) {
wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
}
if (wordDiff == NAME_NO_MATCH) {
continue;
}
final ServiceInfo serviceInfo = info.getServiceInfo();
final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
.flattenToString();
final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(context,
VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
results.add(new SearchResult.Builder()
.setTitle(title)
.setSummary(summary)
.setRank(wordDiff)
.setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key))
.addBreadcrumbs(getVirtualKeyboardBreadCrumb())
.setPayload(new ResultPayload(intent))
.build());
}
return results;
}
private List<String> getPhysicalKeyboardBreadCrumb() {
if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
final Context context = getContext();
mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
context, PHYSICAL_KEYBOARD_FRAGMENT,
context.getString(R.string.physical_keyboard_title));
}
return mPhysicalKeyboardBreadcrumb;
}
private List<String> getVirtualKeyboardBreadCrumb() {
if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
final Context context = getContext();
mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
context, VIRTUAL_KEYBOARD_FRAGMENT,
context.getString(R.string.add_virtual_keyboard));
}
return mVirtualKeyboardBreadcrumb;
}
private List<InputDevice> getPhysicalFullKeyboards() {
final List<InputDevice> keyboards = new ArrayList<>();
final int[] deviceIds = InputDevice.getDeviceIds();
if (deviceIds != null) {
for (int deviceId : deviceIds) {
final InputDevice device = InputDevice.getDevice(deviceId);
if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
keyboards.add(device);
}
}
}
return keyboards;
}
private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
final int subtypeCount = imi.getSubtypeCount();
final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
for (int index = 0; index < subtypeCount; index++) {
allSubtypes.add(imi.getSubtypeAt(index));
}
return allSubtypes;
}
}

View File

@@ -48,6 +48,11 @@ public interface SearchFeatureProvider {
AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
String query);
/**
* Returns a new loader to search input devices.
*/
InputDeviceResultLoader getInputDeviceResultLoader(Context context, String query);
/**
* Returns a new loader to get all recently saved queries search terms.
*/

View File

@@ -61,6 +61,11 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
getSiteMapManager());
}
@Override
public InputDeviceResultLoader getInputDeviceResultLoader(Context context, String query) {
return new InputDeviceResultLoader(context, cleanQuery(query), getSiteMapManager());
}
@Override
public SavedQueryLoader getSavedQueryLoader(Context context) {
return new SavedQueryLoader(context);

View File

@@ -84,8 +84,10 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
static final int LOADER_ID_INSTALLED_APPS = 2;
@VisibleForTesting
static final int LOADER_ID_ACCESSIBILITY_SERVICES = 3;
@VisibleForTesting
static final int LOADER_ID_INPUT_DEVICES = 4;
private static final int NUM_QUERY_LOADERS = 3;
private static final int NUM_QUERY_LOADERS = 4;
@VisibleForTesting
AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);
@@ -284,6 +286,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
loaderManager.destroyLoader(LOADER_ID_DATABASE);
loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS);
loaderManager.destroyLoader(LOADER_ID_ACCESSIBILITY_SERVICES);
loaderManager.destroyLoader(LOADER_ID_INPUT_DEVICES);
mShowingSavedQuery = true;
mSavedQueryController.loadSavedQueries();
mSearchFeatureProvider.hideFeedbackButton();
@@ -314,6 +317,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
case LOADER_ID_ACCESSIBILITY_SERVICES:
return mSearchFeatureProvider.getAccessibilityServiceResultLoader(activity, mQuery);
case LOADER_ID_INPUT_DEVICES:
return mSearchFeatureProvider.getInputDeviceResultLoader(activity, mQuery);
default:
return null;
}
@@ -351,6 +356,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
loaderManager.initLoader(
LOADER_ID_ACCESSIBILITY_SERVICES, null /* args */, this /* callback */);
loaderManager.initLoader(
LOADER_ID_INPUT_DEVICES, null /* args */, this /* callback */);
}
requery();
@@ -392,6 +399,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
loaderManager.restartLoader(LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
loaderManager.restartLoader(LOADER_ID_ACCESSIBILITY_SERVICES, null /* args */,
this /* callback */);
loaderManager.restartLoader(LOADER_ID_INPUT_DEVICES, null /* args */,
this /* callback */);
}
public String getQuery() {

View File

@@ -57,7 +57,9 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
@VisibleForTesting
static final String APP_RESULTS_LOADER_KEY = InstalledAppResultLoader.class.getName();
@VisibleForTesting
static final String ACCESSIBLITY_LOADER_KEY = AccessibilityServiceResultLoader.class.getName();
static final String ACCESSIBILITY_LOADER_KEY = AccessibilityServiceResultLoader.class.getName();
@VisibleForTesting
static final String INPUT_DEVICE_LOADER_KEY = InputDeviceResultLoader.class.getName();
@VisibleForTesting
static final int MSG_RANKING_TIMED_OUT = 1;
@@ -265,17 +267,21 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
List<? extends SearchResult> installedAppResults =
getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
List<? extends SearchResult> accessibilityResults =
getSortedLoadedResults(ACCESSIBLITY_LOADER_KEY);
getSortedLoadedResults(ACCESSIBILITY_LOADER_KEY);
List<? extends SearchResult> inputDeviceResults =
getSortedLoadedResults(INPUT_DEVICE_LOADER_KEY);
int dbSize = databaseResults.size();
int appSize = installedAppResults.size();
int a11ySize = accessibilityResults.size();
int inputDeviceSize = inputDeviceResults.size();
int dbIndex = 0;
int appIndex = 0;
int a11yIndex = 0;
int inputDeviceIndex = 0;
int rank = SearchResult.TOP_RANK;
// TODO: We need a helper method to do k-way merge.
mStaticallyRankedSearchResults.clear();
while (rank <= SearchResult.BOTTOM_RANK) {
while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
@@ -287,6 +293,10 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
while ((a11yIndex < a11ySize) && (accessibilityResults.get(a11yIndex).rank == rank)) {
mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
}
while (inputDeviceIndex < inputDeviceSize
&& inputDeviceResults.get(inputDeviceIndex).rank == rank) {
mStaticallyRankedSearchResults.add(inputDeviceResults.get(inputDeviceIndex++));
}
rank++;
}
@@ -299,6 +309,9 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
while(a11yIndex < a11ySize) {
mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
}
while (inputDeviceIndex < inputDeviceSize) {
mStaticallyRankedSearchResults.add(inputDeviceResults.get(inputDeviceIndex++));
}
}
private void updateSearchResults() {
@@ -332,12 +345,16 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
List<? extends SearchResult> installedAppResults =
getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
List<? extends SearchResult> accessibilityResults =
getSortedLoadedResults(ACCESSIBLITY_LOADER_KEY);
getSortedLoadedResults(ACCESSIBILITY_LOADER_KEY);
List<? extends SearchResult> inputDeviceResults =
getSortedLoadedResults(INPUT_DEVICE_LOADER_KEY);
int dbSize = databaseResults.size();
int appSize = installedAppResults.size();
int a11ySize = accessibilityResults.size();
int inputDeviceSize = inputDeviceResults.size();
final List<SearchResult> asyncRankingResults = new ArrayList<>(dbSize + appSize + a11ySize);
final List<SearchResult> asyncRankingResults = new ArrayList<>(
dbSize + appSize + a11ySize + inputDeviceSize);
TreeSet<SearchResult> dbResultsSortedByScores = new TreeSet<>(
new Comparator<SearchResult>() {
@Override
@@ -358,13 +375,13 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
// Other results are not ranked by async ranking and appended at the end of the list.
asyncRankingResults.addAll(installedAppResults);
asyncRankingResults.addAll(accessibilityResults);
asyncRankingResults.addAll(inputDeviceResults);
return asyncRankingResults;
}
@VisibleForTesting
Set<? extends SearchResult> getUnsortedLoadedResults(String loaderKey) {
return mResultsMap.containsKey(loaderKey) ?
mResultsMap.get(loaderKey) : new HashSet<SearchResult>();
return mResultsMap.containsKey(loaderKey) ? mResultsMap.get(loaderKey) : new HashSet<>();
}
@VisibleForTesting

View File

@@ -30,7 +30,6 @@ import com.android.settings.password.ChooseLockPattern.IntentBuilder;
import com.android.settings.password.SetupChooseLockPattern;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.testutils.shadow.ShadowUtils;
@@ -49,7 +48,6 @@ import org.robolectric.res.builder.RobolectricPackageManager.ComponentState;
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class,
ShadowUtils.class
})

View File

@@ -16,6 +16,17 @@
package com.android.settings.applications;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -35,7 +46,6 @@ import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.SettingsShadowResources.SettingsShadowTheme;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.widget.LoadingViewController;
import com.android.settingslib.applications.ApplicationsState;
@@ -53,17 +63,6 @@ import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link ManageApplications}.
*/
@@ -74,7 +73,6 @@ import static org.mockito.Mockito.when;
shadows = {
SettingsShadowResources.class,
SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class
})
public class ManageApplicationsTest {

View File

@@ -15,6 +15,19 @@
*/
package com.android.settings.dashboard;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -38,7 +51,6 @@ import com.android.settings.dashboard.suggestions.SuggestionAdapter;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
@@ -56,26 +68,12 @@ import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class
})
public class DashboardAdapterTest {

View File

@@ -36,7 +36,6 @@ import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.IFingerprintManager;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.testutils.shadow.ShadowUtils;
@@ -61,7 +60,6 @@ import org.robolectric.shadows.ShadowActivity.IntentForResult;
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class,
ShadowUtils.class
})

View File

@@ -30,7 +30,6 @@ import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.IFingerprintManager;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.testutils.shadow.ShadowUtils;
@@ -53,7 +52,6 @@ import org.robolectric.shadows.ShadowAlertDialog;
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class,
ShadowUtils.class
})

View File

@@ -17,6 +17,15 @@
package com.android.settings.fuelgauge;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -27,11 +36,10 @@ import android.support.v7.widget.RecyclerView;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -45,23 +53,12 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEntityHeaderController.class
})
public class BatteryHeaderPreferenceControllerTest {

View File

@@ -16,15 +16,17 @@
package com.android.settings.fuelgauge;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.ColorFilter;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.SettingsShadowResources.SettingsShadowTheme;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import org.junit.Before;
import org.junit.Test;
@@ -34,10 +36,6 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@RunWith(SettingsRobolectricTestRunner.class)
// TODO: Consider making the shadow class set global using a robolectric.properties file.
@Config(manifest = TestConfig.MANIFEST_PATH,
@@ -45,7 +43,6 @@ import static org.mockito.Mockito.verify;
shadows = {
SettingsShadowResources.class,
SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class
})
public class BatteryMeterViewTest {
private static final int BATTERY_LEVEL = 100;

View File

@@ -18,9 +18,7 @@ package com.android.settings.fuelgauge;
import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADDITIONAL_BATTERY_INFO;
import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_HIGH_POWER_APPS;
import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_TOGGLE_APPS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
@@ -34,8 +32,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import android.app.LoaderManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -68,7 +64,6 @@ import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.XmlTestUtils;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settingslib.core.AbstractPreferenceController;
import org.junit.Before;
@@ -95,7 +90,6 @@ import java.util.List;
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class
})
public class PowerUsageSummaryTest {
private static final String[] PACKAGE_NAMES = {"com.app1", "com.app2"};

View File

@@ -31,7 +31,6 @@ import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragme
import com.android.settings.password.ChooseLockPassword.IntentBuilder;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.setupwizardlib.GlifLayout;
@@ -52,7 +51,6 @@ import org.robolectric.shadows.ShadowDrawable;
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class,
ShadowUtils.class
})

View File

@@ -29,7 +29,6 @@ import com.android.settings.password.ChooseLockPattern.ChooseLockPatternFragment
import com.android.settings.password.ChooseLockPattern.IntentBuilder;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.setupwizardlib.GlifLayout;
@@ -48,7 +47,6 @@ import org.robolectric.shadows.ShadowDrawable;
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class,
ShadowUtils.class
})

View File

@@ -34,7 +34,6 @@ import com.android.settings.password.ChooseLockPassword.IntentBuilder;
import com.android.settings.password.SetupChooseLockPassword.SetupChooseLockPasswordFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.testutils.shadow.ShadowUtils;
@@ -62,7 +61,6 @@ import java.util.List;
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class,
ShadowUtils.class
})

View File

@@ -27,7 +27,6 @@ import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settings.testutils.shadow.ShadowUtils;
@@ -40,13 +39,11 @@ import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowAlertDialog;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(
manifest = TestConfig.MANIFEST_PATH,
@Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
ShadowDynamicIndexableContentMonitor.class,
ShadowEventLogWriter.class,
ShadowUtils.class
})

View File

@@ -1,563 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Application;
import android.app.LoaderManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Bundle;
import android.print.PrintManager;
import android.print.PrintServicesLoader;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.provider.UserDictionary;
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.content.PackageMonitor;
import com.android.settings.TestConfig;
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import com.android.settings.inputmethod.VirtualKeyboardFragment;
import com.android.settings.language.LanguageAndInputSettings;
import com.android.settings.print.PrintSettingsFragment;
import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowActivityWithLoadManager;
import com.android.settings.testutils.shadow.ShadowContextImplWithRegisterReceiver;
import com.android.settings.testutils.shadow.ShadowInputManager;
import com.android.settings.testutils.shadow.ShadowInputMethodManagerWithMethodList;
import com.android.settings.testutils.shadow.ShadowPackageMonitor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.internal.ShadowExtractor;
import org.robolectric.res.builder.RobolectricPackageManager;
import org.robolectric.shadows.ShadowAccessibilityManager;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowContentResolver;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(
manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
shadows = {
ShadowActivityWithLoadManager.class,
ShadowContextImplWithRegisterReceiver.class,
ShadowInputManager.class,
ShadowInputMethodManagerWithMethodList.class,
ShadowPackageMonitor.class,
}
)
public class DynamicIndexableContentMonitorTest {
private static final int LOADER_ID = 1234;
private static final String A11Y_PACKAGE_1 = "a11y-1";
private static final String A11Y_PACKAGE_2 = "a11y-2";
private static final String IME_PACKAGE_1 = "ime-1";
private static final String IME_PACKAGE_2 = "ime-2";
@Mock
private LoaderManager mLoaderManager;
@Mock
private DatabaseIndexingManager mIndexManager;
private Activity mActivity;
private InputManager mInputManager;
private ShadowContextImplWithRegisterReceiver mShadowContextImpl;
private ShadowActivityWithLoadManager mShadowActivity;
private ShadowAccessibilityManager mShadowAccessibilityManager;
private ShadowInputMethodManagerWithMethodList mShadowInputMethodManager;
private RobolectricPackageManager mRobolectricPackageManager;
private final DynamicIndexableContentMonitor mMonitor = new DynamicIndexableContentMonitor();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mActivity = Robolectric.buildActivity(Activity.class).get();
mInputManager = InputManager.getInstance();
// Robolectric shadows.
mShadowContextImpl = (ShadowContextImplWithRegisterReceiver) ShadowExtractor.extract(
((Application) ShadowApplication.getInstance().getApplicationContext())
.getBaseContext());
mShadowActivity = (ShadowActivityWithLoadManager) ShadowExtractor.extract(mActivity);
mShadowAccessibilityManager = (ShadowAccessibilityManager) ShadowExtractor.extract(
mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE));
mShadowInputMethodManager = (ShadowInputMethodManagerWithMethodList) ShadowExtractor
.extract(mActivity.getSystemService(Context.INPUT_METHOD_SERVICE));
mRobolectricPackageManager = RuntimeEnvironment.getRobolectricPackageManager();
// Setup shadows.
mShadowContextImpl.setSystemService(Context.PRINT_SERVICE, mock(PrintManager.class));
mShadowContextImpl.setSystemService(Context.INPUT_SERVICE, mInputManager);
mShadowActivity.setLoaderManager(mLoaderManager);
mShadowAccessibilityManager.setInstalledAccessibilityServiceList(Collections.emptyList());
mShadowInputMethodManager.setInputMethodList(Collections.emptyList());
mRobolectricPackageManager.setSystemFeature(PackageManager.FEATURE_PRINTING, true);
mRobolectricPackageManager.setSystemFeature(PackageManager.FEATURE_INPUT_METHODS, true);
}
@After
public void shutDown() {
mMonitor.unregister(mActivity, LOADER_ID);
// BroadcastReceiver must be unregistered.
assertThat(extractPackageMonitor()).isNull();
DynamicIndexableContentMonitor.resetForTesting();
mRobolectricPackageManager.reset();
DatabaseTestUtils.clearDb(mActivity);
}
@Test
public void testLockedUser() {
mMonitor.register(mActivity, LOADER_ID, mIndexManager, false /* isUserUnlocked */);
// No loader procedure happens.
verify(mLoaderManager, never()).initLoader(
anyInt(), any(Bundle.class), any(LoaderManager.LoaderCallbacks.class));
// No indexing happens.
verify(mIndexManager, never()).updateFromClassNameResource(
anyString(), anyBoolean());
mMonitor.unregister(mActivity, LOADER_ID);
// No destroy loader should happen.
verify(mLoaderManager, never()).destroyLoader(anyInt());
}
@Test
public void testWithNoPrintingFeature() {
mRobolectricPackageManager.setSystemFeature(PackageManager.FEATURE_PRINTING, false);
mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
// No loader procedure happens.
verify(mLoaderManager, never()).initLoader(
anyInt(), any(Bundle.class), any(LoaderManager.LoaderCallbacks.class));
verifyNoIndexing(PrintSettingsFragment.class);
mMonitor.unregister(mActivity, LOADER_ID);
// No destroy loader should happen.
verify(mLoaderManager, never()).destroyLoader(anyInt());
// BroadcastReceiver must be unregistered.
assertThat(extractPackageMonitor()).isNull();
// To suppress spurious test fail in {@link #shutDown()}.
mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
}
@Test
public void testPrinterServiceIndex() {
mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
// Loader procedure happens.
verify(mLoaderManager, only()).initLoader(LOADER_ID, null, mMonitor);
// Loading print services happens.
final Loader<List<PrintServiceInfo>> loader =
mMonitor.onCreateLoader(LOADER_ID, null /* args */);
assertThat(loader).isInstanceOf(PrintServicesLoader.class);
verifyNoIndexing(PrintSettingsFragment.class);
mMonitor.onLoadFinished(loader, Collections.emptyList());
verifyIncrementalIndexing(PrintSettingsFragment.class);
}
@Test
public void testInputDevicesMonitor() {
mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
// Rebuild indexing should happen.
verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
// Input monitor should be registered to InputManager.
final InputManager.InputDeviceListener listener = extactInputDeviceListener();
assertThat(listener).isNotNull();
/*
* Nothing happens on successive register calls.
*/
mMonitor.unregister(mActivity, LOADER_ID);
reset(mIndexManager);
mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
verifyNoIndexing(PhysicalKeyboardFragment.class);
assertThat(extactInputDeviceListener()).isEqualTo(listener);
/*
* A device is added.
*/
reset(mIndexManager);
listener.onInputDeviceAdded(1 /* deviceId */);
verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
/*
* A device is removed.
*/
reset(mIndexManager);
listener.onInputDeviceRemoved(2 /* deviceId */);
verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
/*
* A device is changed.
*/
reset(mIndexManager);
listener.onInputDeviceChanged(3 /* deviceId */);
verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
}
@Test
public void testInputMethodServicesMonitor() throws Exception {
mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
final Uri enabledInputMethodsContentUri = Settings.Secure.getUriFor(
Settings.Secure.ENABLED_INPUT_METHODS);
// Content observer should be registered.
final ContentObserver observer = extractContentObserver(enabledInputMethodsContentUri);
assertThat(observer).isNotNull();
/*
* When an input method service package is installed, incremental indexing happen.
*/
reset(mIndexManager);
installInputMethodService(IME_PACKAGE_1);
verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
/*
* When another input method service package is installed, incremental indexing happens.
*/
reset(mIndexManager);
installInputMethodService(IME_PACKAGE_2);
verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
/*
* When an input method service is disabled, rebuild indexing happens.
*/
reset(mIndexManager);
disableInstalledPackage(IME_PACKAGE_1);
verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
/*
* When an input method service is enabled, incremental indexing happens.
*/
reset(mIndexManager);
enableInstalledPackage(IME_PACKAGE_1);
verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
/*
* When an input method service package is uninstalled, rebuild indexing happens.
*/
reset(mIndexManager);
uninstallInputMethodService(IME_PACKAGE_1);
verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
/*
* When an accessibility service package is installed, nothing happens.
*/
reset(mIndexManager);
installAccessibilityService(A11Y_PACKAGE_1);
verifyNoIndexing(VirtualKeyboardFragment.class);
verifyNoIndexing(AvailableVirtualKeyboardFragment.class);
/*
* When enabled IMEs list is changed, rebuild indexing happens.
*/
reset(mIndexManager);
observer.onChange(false /* selfChange */, enabledInputMethodsContentUri);
verifyIncrementalIndexing(VirtualKeyboardFragment.class);
verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
}
@Test
public void testUserDictionaryChangeMonitor() throws Exception {
mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
// Content observer should be registered.
final ContentObserver observer = extractContentObserver(UserDictionary.Words.CONTENT_URI);
assertThat(observer).isNotNull();
verifyIncrementalIndexing(LanguageAndInputSettings.class);
/*
* When user dictionary content is changed, rebuild indexing happens.
*/
reset(mIndexManager);
observer.onChange(false /* selfChange */, UserDictionary.Words.CONTENT_URI);
verifyIncrementalIndexing(LanguageAndInputSettings.class);
}
/*
* Verification helpers.
*/
private void verifyNoIndexing(Class<?> indexingClass) {
verify(mIndexManager, never()).updateFromClassNameResource(eq(indexingClass.getName()),
anyBoolean());
}
private void verifyIncrementalIndexing(Class<?> indexingClass) {
verify(mIndexManager, times(1)).updateFromClassNameResource(indexingClass.getName(),
true /* includeInSearchResults */);
verify(mIndexManager, never()).updateFromClassNameResource(indexingClass.getName(),
false /* includeInSearchResults */);
}
/*
* Testing helper methods.
*/
private InputManager.InputDeviceListener extactInputDeviceListener() {
List<InputManager.InputDeviceListener> listeners = ((ShadowInputManager) ShadowExtractor
.extract(mInputManager))
.getRegisteredInputDeviceListeners();
InputManager.InputDeviceListener inputDeviceListener = null;
for (InputManager.InputDeviceListener listener : listeners) {
if (isUnderTest(listener)) {
if (inputDeviceListener != null) {
assertThat(listener).isEqualTo(inputDeviceListener);
} else {
inputDeviceListener = listener;
}
}
}
return inputDeviceListener;
}
private PackageMonitor extractPackageMonitor() {
List<ShadowApplication.Wrapper> receivers = ShadowApplication.getInstance()
.getRegisteredReceivers();
PackageMonitor packageMonitor = null;
for (ShadowApplication.Wrapper wrapper : receivers) {
BroadcastReceiver receiver = wrapper.getBroadcastReceiver();
if (isUnderTest(receiver) && receiver instanceof PackageMonitor) {
if (packageMonitor != null) {
assertThat(receiver).isEqualTo(packageMonitor);
} else {
packageMonitor = (PackageMonitor) receiver;
}
}
}
return packageMonitor;
}
private ContentObserver extractContentObserver(Uri uri) {
ShadowContentResolver contentResolver = (ShadowContentResolver) ShadowExtractor
.extract(mActivity.getContentResolver());
Collection<ContentObserver> observers = contentResolver.getContentObservers(uri);
ContentObserver contentObserver = null;
for (ContentObserver observer : observers) {
if (isUnderTest(observer)) {
if (contentObserver != null) {
assertThat(observer).isEqualTo(contentObserver);
} else {
contentObserver = observer;
}
}
}
return contentObserver;
}
private void enableInstalledPackage(String packageName) {
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0 /* flags */);
extractPackageMonitor().onPackageModified(packageName);
Robolectric.flushBackgroundThreadScheduler();
}
private void disableInstalledPackage(String packageName) {
((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
extractPackageMonitor().onPackageModified(packageName);
Robolectric.flushBackgroundThreadScheduler();
}
private void installAccessibilityService(String packageName) throws Exception {
final AccessibilityServiceInfo serviceToAdd = buildAccessibilityServiceInfo(packageName);
final List<AccessibilityServiceInfo> services = new ArrayList<>();
services.addAll(mShadowAccessibilityManager.getInstalledAccessibilityServiceList());
services.add(serviceToAdd);
mShadowAccessibilityManager.setInstalledAccessibilityServiceList(services);
final Intent intent = DynamicIndexableContentMonitor
.getAccessibilityServiceIntent(packageName);
mRobolectricPackageManager.addResolveInfoForIntent(intent, serviceToAdd.getResolveInfo());
mRobolectricPackageManager.addPackage(packageName);
extractPackageMonitor()
.onPackageAppeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
}
private void uninstallAccessibilityService(String packageName) throws Exception {
final AccessibilityServiceInfo serviceToRemove = buildAccessibilityServiceInfo(packageName);
final List<AccessibilityServiceInfo> services = new ArrayList<>();
services.addAll(mShadowAccessibilityManager.getInstalledAccessibilityServiceList());
services.remove(serviceToRemove);
mShadowAccessibilityManager.setInstalledAccessibilityServiceList(services);
final Intent intent = DynamicIndexableContentMonitor
.getAccessibilityServiceIntent(packageName);
mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName);
mRobolectricPackageManager.removePackage(packageName);
extractPackageMonitor()
.onPackageDisappeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
}
private void installInputMethodService(String packageName) throws Exception {
final ResolveInfo resolveInfoToAdd = buildResolveInfo(packageName, "imeService");
final InputMethodInfo serviceToAdd = buildInputMethodInfo(resolveInfoToAdd);
final List<InputMethodInfo> services = new ArrayList<>();
services.addAll(mShadowInputMethodManager.getInputMethodList());
services.add(serviceToAdd);
mShadowInputMethodManager.setInputMethodList(services);
final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName);
mRobolectricPackageManager.addResolveInfoForIntent(intent, resolveInfoToAdd);
mRobolectricPackageManager.addPackage(packageName);
extractPackageMonitor()
.onPackageAppeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
}
private void uninstallInputMethodService(String packageName) throws Exception {
final ResolveInfo resolveInfoToRemove = buildResolveInfo(packageName, "imeService");
final InputMethodInfo serviceToRemove = buildInputMethodInfo(resolveInfoToRemove);
final List<InputMethodInfo> services = new ArrayList<>();
services.addAll(mShadowInputMethodManager.getInputMethodList());
services.remove(serviceToRemove);
mShadowInputMethodManager.setInputMethodList(services);
final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName);
mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName);
mRobolectricPackageManager.removePackage(packageName);
extractPackageMonitor()
.onPackageDisappeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
Robolectric.flushBackgroundThreadScheduler();
}
private AccessibilityServiceInfo buildAccessibilityServiceInfo(String packageName)
throws IOException, XmlPullParserException {
return new AccessibilityServiceInfo(
buildResolveInfo(packageName, "A11yService"), mActivity);
}
private static InputMethodInfo buildInputMethodInfo(ResolveInfo resolveInfo) {
return new InputMethodInfo(resolveInfo, false /* isAuxIme */, "SettingsActivity",
null /* subtypes */, 0 /* defaultResId */, false /* forceDefault */);
}
private static ResolveInfo buildResolveInfo(String packageName, String className) {
final ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.serviceInfo = new ServiceInfo();
resolveInfo.serviceInfo.packageName = packageName;
resolveInfo.serviceInfo.name = className;
// To workaround that RobolectricPackageManager.removeResolveInfosForIntent() only works
// for activity/broadcast resolver.
resolveInfo.activityInfo = new ActivityInfo();
resolveInfo.activityInfo.packageName = packageName;
resolveInfo.activityInfo.name = className;
return resolveInfo;
}
private static boolean isUnderTest(Object object) {
return object.getClass().getName().startsWith(
DynamicIndexableContentMonitor.class.getName());
}
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.search;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static com.android.settings.search.InputDeviceResultLoader.PHYSICAL_KEYBOARD_FRAGMENT;
import static com.android.settings.search.InputDeviceResultLoader.VIRTUAL_KEYBOARD_FRAGMENT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.hardware.input.InputManager;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowInputDevice;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
shadows = {
ShadowInputDevice.class
})
public class InputDeviceResultLoaderTest {
private static final String QUERY = "test_query";
private static final List<String> PHYSICAL_KEYBOARD_BREADCRUMB;
private static final List<String> VIRTUAL_KEYBOARD_BREADCRUMB;
static {
PHYSICAL_KEYBOARD_BREADCRUMB = new ArrayList<>();
VIRTUAL_KEYBOARD_BREADCRUMB = new ArrayList<>();
PHYSICAL_KEYBOARD_BREADCRUMB.add("Settings");
PHYSICAL_KEYBOARD_BREADCRUMB.add("physical keyboard");
VIRTUAL_KEYBOARD_BREADCRUMB.add("Settings");
VIRTUAL_KEYBOARD_BREADCRUMB.add("virtual keyboard");
}
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@Mock
private SiteMapManager mSiteMapManager;
@Mock
private InputManager mInputManager;
@Mock
private InputMethodManager mImm;
@Mock
private PackageManager mPackageManager;
private InputDeviceResultLoader mLoader;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getApplicationContext()).thenReturn(mContext);
when(mContext.getSystemService(Context.INPUT_SERVICE))
.thenReturn(mInputManager);
when(mContext.getSystemService(INPUT_METHOD_SERVICE))
.thenReturn(mImm);
when(mContext.getPackageManager())
.thenReturn(mPackageManager);
when(mContext.getString(anyInt()))
.thenAnswer(invocation -> RuntimeEnvironment.application.getString(
(Integer) invocation.getArguments()[0]));
mLoader = new InputDeviceResultLoader(mContext, QUERY, mSiteMapManager);
}
@After
public void tearDown() {
ShadowInputDevice.reset();
}
@Test
public void query_noKeyboard_shouldNotReturnAnything() {
assertThat(mLoader.loadInBackground()).isEmpty();
}
@Test
public void query_hasPhysicalKeyboard_match() {
addPhysicalKeyboard(QUERY);
when(mSiteMapManager.buildBreadCrumb(mContext, PHYSICAL_KEYBOARD_FRAGMENT,
RuntimeEnvironment.application.getString(R.string.physical_keyboard_title)))
.thenReturn(PHYSICAL_KEYBOARD_BREADCRUMB);
final List<SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
assertThat(results).hasSize(1);
assertThat(results.get(0).title).isEqualTo(QUERY);
assertThat(results.get(0).breadcrumbs)
.containsExactlyElementsIn(PHYSICAL_KEYBOARD_BREADCRUMB);
}
@Test
public void query_hasVirtualKeyboard_match() {
addVirtualKeyboard(QUERY);
when(mSiteMapManager.buildBreadCrumb(mContext, VIRTUAL_KEYBOARD_FRAGMENT,
RuntimeEnvironment.application.getString(R.string.add_virtual_keyboard)))
.thenReturn(VIRTUAL_KEYBOARD_BREADCRUMB);
final List<SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
assertThat(results).hasSize(1);
assertThat(results.get(0).title).isEqualTo(QUERY);
assertThat(results.get(0).breadcrumbs)
.containsExactlyElementsIn(VIRTUAL_KEYBOARD_BREADCRUMB);
}
@Test
public void query_hasPhysicalVirtualKeyboard_doNotMatch() {
addPhysicalKeyboard("abc");
addVirtualKeyboard("def");
assertThat(mLoader.loadInBackground()).isEmpty();
verifyZeroInteractions(mSiteMapManager);
}
private void addPhysicalKeyboard(String name) {
final InputDevice device = mock(InputDevice.class);
when(device.isVirtual()).thenReturn(false);
when(device.isFullKeyboard()).thenReturn(true);
when(device.getName()).thenReturn(name);
ShadowInputDevice.sDeviceIds = new int[]{0};
ShadowInputDevice.addDevice(0, device);
}
private void addVirtualKeyboard(String name) {
final List<InputMethodInfo> imis = new ArrayList<>();
final InputMethodInfo info = mock(InputMethodInfo.class);
imis.add(info);
when(info.getServiceInfo()).thenReturn(new ServiceInfo());
when(info.loadLabel(mPackageManager)).thenReturn(name);
info.getServiceInfo().packageName = "pkg";
info.getServiceInfo().name = "class";
when(mImm.getInputMethodList()).thenReturn(imis);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.search;
import android.content.Context;
import java.util.HashSet;
import java.util.Set;
public class MockAccessibilityLoader extends AccessibilityServiceResultLoader {
public MockAccessibilityLoader(Context context) {
super(context, "test_query", null);
}
@Override
public Set<? extends SearchResult> loadInBackground() {
return new HashSet<>();
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
}

View File

@@ -21,9 +21,8 @@ import android.content.Context;
import java.util.HashSet;
import java.util.Set;
public class MockAccessiblityLoader extends AccessibilityServiceResultLoader {
public MockAccessiblityLoader(Context context) {
public class MockInputDeviceResultLoader extends InputDeviceResultLoader {
public MockInputDeviceResultLoader(Context context) {
super(context, "test_query", null);
}

View File

@@ -17,6 +17,20 @@
package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
@@ -28,10 +42,10 @@ import android.view.View;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import org.junit.After;
@@ -52,20 +66,6 @@ import org.robolectric.util.ReflectionHelpers;
import java.util.Set;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
@@ -83,6 +83,8 @@ public class SearchFragmentTest {
private InstalledAppResultLoader mInstalledAppResultLoader;
@Mock
private AccessibilityServiceResultLoader mAccessibilityServiceResultLoader;
@Mock
private InputDeviceResultLoader mInputDeviceResultLoader;
@Mock
private SavedQueryLoader mSavedQueryLoader;
@@ -118,6 +120,9 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
@@ -178,6 +183,9 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
@@ -236,6 +244,9 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController =
@@ -270,6 +281,9 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
@@ -349,7 +363,10 @@ public class SearchFragmentTest {
.thenReturn(new MockAppLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(new MockAccessiblityLoader(RuntimeEnvironment.application));
.thenReturn(new MockAccessibilityLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(new MockInputDeviceResultLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController =

View File

@@ -1,22 +0,0 @@
package com.android.settings.testutils.shadow;
import android.app.Activity;
import android.os.UserManager;
import com.android.settings.search.DynamicIndexableContentMonitor;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
/**
* A shadow class of {@link DynamicIndexableContentMonitor}. The real implementation of
* {@link DynamicIndexableContentMonitor#register} calls {@link UserManager#isUserUnlocked()}, which
* Robolectric has not yet been updated to support, so throws a NoSuchMethodError exception.
*/
// TODO: Delete this once Robolectric is updated to the latest SDK.
@Implements(DynamicIndexableContentMonitor.class)
public class ShadowDynamicIndexableContentMonitor {
@Implementation
public void register(Activity activity, int loaderId) {
}
}