Refactor DynamicIndexableContentMonitor
Refactor content monitoring code into a few singletons to keep alive while Settings app is running. Bug: 32995210 Test: Manually installing/uninstalling AOSP LatinIME.apk while Settings app is/isn't running, then search AOSP. Test: Connecting/Disconnecting Anker bluetooth keyboard while Settings app is/isn't running, then search Anker. Test: Added Robolectric test for DynamicIndexableContentMonitor. Change-Id: I588e33be169fc9677d41c3daa59ab400f04f6419
This commit is contained in:
@@ -20,6 +20,7 @@ import android.accessibilityservice.AccessibilityService;
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.app.Activity;
|
||||
import android.app.LoaderManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
@@ -30,17 +31,17 @@ import android.database.ContentObserver;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.print.PrintManager;
|
||||
import android.print.PrintServicesLoader;
|
||||
import android.printservice.PrintServiceInfo;
|
||||
import android.provider.UserDictionary;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.inputmethod.InputMethod;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
@@ -52,227 +53,100 @@ import com.android.settings.print.PrintSettingsFragment;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class DynamicIndexableContentMonitor extends PackageMonitor implements
|
||||
InputManager.InputDeviceListener,
|
||||
public final class DynamicIndexableContentMonitor implements
|
||||
LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
|
||||
private static final String TAG = "DynamicIndexableContentMonitor";
|
||||
// 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;
|
||||
|
||||
private static final int MSG_PACKAGE_AVAILABLE = 1;
|
||||
private static final int MSG_PACKAGE_UNAVAILABLE = 2;
|
||||
|
||||
private final List<String> mAccessibilityServices = new ArrayList<String>();
|
||||
private final List<String> mImeServices = new ArrayList<String>();
|
||||
|
||||
private final Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_PACKAGE_AVAILABLE: {
|
||||
String packageName = (String) msg.obj;
|
||||
handlePackageAvailable(packageName);
|
||||
} break;
|
||||
|
||||
case MSG_PACKAGE_UNAVAILABLE: {
|
||||
String packageName = (String) msg.obj;
|
||||
handlePackageUnavailable(packageName);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final ContentObserver mUserDictionaryContentObserver =
|
||||
new UserDictionaryContentObserver(mHandler);
|
||||
@VisibleForTesting
|
||||
static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000;
|
||||
|
||||
// Null if not initialized.
|
||||
@Nullable private Index mIndex;
|
||||
private Context mContext;
|
||||
private boolean mHasFeatureIme;
|
||||
private boolean mRegistered;
|
||||
private boolean mHasFeaturePrinting;
|
||||
|
||||
private static Intent getAccessibilityServiceIntent(String packageName) {
|
||||
@VisibleForTesting
|
||||
static Intent getAccessibilityServiceIntent(String packageName) {
|
||||
final Intent intent = new Intent(AccessibilityService.SERVICE_INTERFACE);
|
||||
intent.setPackage(packageName);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static Intent getIMEServiceIntent(String packageName) {
|
||||
final Intent intent = new Intent("android.view.InputMethod");
|
||||
@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();
|
||||
PackageChangeMonitor.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) {
|
||||
mContext = activity;
|
||||
final boolean isUserUnlocked = activity
|
||||
.getSystemService(UserManager.class)
|
||||
.isUserUnlocked();
|
||||
register(activity, loaderId, Index.getInstance(activity), isUserUnlocked);
|
||||
}
|
||||
|
||||
if (!mContext.getSystemService(UserManager.class).isUserUnlocked()) {
|
||||
/**
|
||||
* For testing to inject {@link Index} 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, Index index, boolean isUserUnlocked) {
|
||||
if (!isUserUnlocked) {
|
||||
Log.w(TAG, "Skipping content monitoring because user is locked");
|
||||
mRegistered = false;
|
||||
return;
|
||||
} else {
|
||||
mRegistered = true;
|
||||
}
|
||||
mContext = activity;
|
||||
mIndex = index;
|
||||
|
||||
boolean hasFeaturePrinting = mContext.getPackageManager().hasSystemFeature(
|
||||
PackageManager.FEATURE_PRINTING);
|
||||
mHasFeatureIme = mContext.getPackageManager().hasSystemFeature(
|
||||
PackageManager.FEATURE_INPUT_METHODS);
|
||||
|
||||
// Cache accessibility service packages to know when they go away.
|
||||
AccessibilityManager accessibilityManager = (AccessibilityManager)
|
||||
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
|
||||
.getInstalledAccessibilityServiceList();
|
||||
final int accessibilityServiceCount = accessibilityServices.size();
|
||||
for (int i = 0; i < accessibilityServiceCount; i++) {
|
||||
AccessibilityServiceInfo accessibilityService = accessibilityServices.get(i);
|
||||
ResolveInfo resolveInfo = accessibilityService.getResolveInfo();
|
||||
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
|
||||
continue;
|
||||
}
|
||||
mAccessibilityServices.add(resolveInfo.serviceInfo.packageName);
|
||||
}
|
||||
|
||||
if (hasFeaturePrinting) {
|
||||
activity.getLoaderManager().initLoader(loaderId, null, this);
|
||||
}
|
||||
|
||||
// Cache IME service packages to know when they go away.
|
||||
if (mHasFeatureIme) {
|
||||
InputMethodManager imeManager = (InputMethodManager)
|
||||
mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
List<InputMethodInfo> inputMethods = imeManager.getInputMethodList();
|
||||
final int inputMethodCount = inputMethods.size();
|
||||
for (int i = 0; i < inputMethodCount; i++) {
|
||||
InputMethodInfo inputMethod = inputMethods.get(i);
|
||||
ServiceInfo serviceInfo = inputMethod.getServiceInfo();
|
||||
if (serviceInfo == null) continue;
|
||||
mImeServices.add(serviceInfo.packageName);
|
||||
}
|
||||
|
||||
// Watch for related content URIs.
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
UserDictionary.Words.CONTENT_URI, true, mUserDictionaryContentObserver);
|
||||
mHasFeaturePrinting = mContext.getPackageManager()
|
||||
.hasSystemFeature(PackageManager.FEATURE_PRINTING);
|
||||
if (mHasFeaturePrinting) {
|
||||
activity.getLoaderManager().initLoader(loaderId, null /* args */, this /* callbacks */);
|
||||
}
|
||||
|
||||
// Watch for input device changes.
|
||||
InputManager inputManager = (InputManager) activity.getSystemService(
|
||||
Context.INPUT_SERVICE);
|
||||
inputManager.registerInputDeviceListener(this, mHandler);
|
||||
InputDevicesMonitor.getInstance().initialize(mContext, mIndex);
|
||||
|
||||
// Start tracking packages.
|
||||
register(activity, Looper.getMainLooper(), UserHandle.CURRENT, false);
|
||||
PackageChangeMonitor.getInstance().initialize(mContext, mIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
if (!mRegistered) return;
|
||||
/**
|
||||
* 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 (mIndex == null) return;
|
||||
|
||||
super.unregister();
|
||||
|
||||
InputManager inputManager = (InputManager) mContext.getSystemService(
|
||||
Context.INPUT_SERVICE);
|
||||
inputManager.unregisterInputDeviceListener(this);
|
||||
|
||||
if (mHasFeatureIme) {
|
||||
mContext.getContentResolver().unregisterContentObserver(
|
||||
mUserDictionaryContentObserver);
|
||||
}
|
||||
|
||||
mAccessibilityServices.clear();
|
||||
mImeServices.clear();
|
||||
}
|
||||
|
||||
// Covers installed, appeared external storage with the package, upgraded.
|
||||
@Override
|
||||
public void onPackageAppeared(String packageName, int uid) {
|
||||
postMessage(MSG_PACKAGE_AVAILABLE, packageName);
|
||||
}
|
||||
|
||||
// Covers uninstalled, removed external storage with the package.
|
||||
@Override
|
||||
public void onPackageDisappeared(String packageName, int uid) {
|
||||
postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
|
||||
}
|
||||
|
||||
// Covers enabled, disabled.
|
||||
@Override
|
||||
public void onPackageModified(String packageName) {
|
||||
super.onPackageModified(packageName);
|
||||
try {
|
||||
final int state = mContext.getPackageManager().getApplicationEnabledSetting(
|
||||
packageName);
|
||||
if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|
||||
|| state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
||||
postMessage(MSG_PACKAGE_AVAILABLE, packageName);
|
||||
} else {
|
||||
postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Package does not exist: " + packageName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
InputMethodAndLanguageSettings.class.getName(), false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
onInputDeviceChanged(deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
InputMethodAndLanguageSettings.class.getName(), true, true);
|
||||
}
|
||||
|
||||
private void postMessage(int what, String packageName) {
|
||||
Message message = mHandler.obtainMessage(what, packageName);
|
||||
mHandler.sendMessageDelayed(message, DELAY_PROCESS_PACKAGE_CHANGE);
|
||||
}
|
||||
|
||||
private void handlePackageAvailable(String packageName) {
|
||||
if (!mAccessibilityServices.contains(packageName)) {
|
||||
final Intent intent = getAccessibilityServiceIntent(packageName);
|
||||
List<?> services = mContext.getPackageManager().queryIntentServices(intent, 0);
|
||||
if (services != null && !services.isEmpty()) {
|
||||
mAccessibilityServices.add(packageName);
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
AccessibilitySettings.class.getName(), false, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (mHasFeatureIme) {
|
||||
if (!mImeServices.contains(packageName)) {
|
||||
Intent intent = getIMEServiceIntent(packageName);
|
||||
List<?> services = mContext.getPackageManager().queryIntentServices(intent, 0);
|
||||
if (services != null && !services.isEmpty()) {
|
||||
mImeServices.add(packageName);
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
InputMethodAndLanguageSettings.class.getName(), false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePackageUnavailable(String packageName) {
|
||||
final int accessibilityIndex = mAccessibilityServices.indexOf(packageName);
|
||||
if (accessibilityIndex >= 0) {
|
||||
mAccessibilityServices.remove(accessibilityIndex);
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
AccessibilitySettings.class.getName(), true, true);
|
||||
}
|
||||
|
||||
if (mHasFeatureIme) {
|
||||
final int imeIndex = mImeServices.indexOf(packageName);
|
||||
if (imeIndex >= 0) {
|
||||
mImeServices.remove(imeIndex);
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
InputMethodAndLanguageSettings.class.getName(), true, true);
|
||||
}
|
||||
if (mHasFeaturePrinting) {
|
||||
activity.getLoaderManager().destroyLoader(loaderId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,8 +160,8 @@ public final class DynamicIndexableContentMonitor extends PackageMonitor impleme
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
|
||||
List<PrintServiceInfo> services) {
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
PrintSettingsFragment.class.getName(), false, true);
|
||||
mIndex.updateFromClassNameResource(PrintSettingsFragment.class.getName(),
|
||||
false /* rebuild */, true /* includeInSearchResult */);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,18 +169,304 @@ public final class DynamicIndexableContentMonitor extends PackageMonitor impleme
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
private final class UserDictionaryContentObserver extends ContentObserver {
|
||||
// A singleton that monitors input devices changes and updates indexes of physical keyboards.
|
||||
private static class InputDevicesMonitor implements InputManager.InputDeviceListener {
|
||||
|
||||
public UserDictionaryContentObserver(Handler handler) {
|
||||
super(handler);
|
||||
// Null if not initialized.
|
||||
@Nullable private Index mIndex;
|
||||
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 (mIndex != null) {
|
||||
mInputManager.unregisterInputDeviceListener(this /* listener */);
|
||||
}
|
||||
mIndex = null;
|
||||
}
|
||||
|
||||
synchronized void initialize(Context context, Index index) {
|
||||
if (mIndex != null) return;
|
||||
mIndex = index;
|
||||
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||
buildIndex(true /* rebuild */);
|
||||
|
||||
// Watch for input device changes.
|
||||
mInputManager.registerInputDeviceListener(this /* listener */, null /* handler */);
|
||||
}
|
||||
|
||||
private void buildIndex(boolean rebuild) {
|
||||
// TODO: Fix landing page to PhysicalKeyboardFragment.
|
||||
mIndex.updateFromClassNameResource(InputMethodAndLanguageSettings.class.getName(),
|
||||
rebuild, true /* includeInSearchResult */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {
|
||||
buildIndex(false /* rebuild */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
buildIndex(true /* rebuild */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
buildIndex(true /* rebuild */);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@Nullable private PackageManager mPackageManager;
|
||||
|
||||
private PackageChangeMonitor() {}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final PackageChangeMonitor INSTANCE = new PackageChangeMonitor();
|
||||
}
|
||||
|
||||
static PackageChangeMonitor getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
synchronized void resetForTesting() {
|
||||
if (mPackageManager != null) {
|
||||
unregister();
|
||||
}
|
||||
mPackageManager = null;
|
||||
AccessibilityServicesMonitor.getInstance().resetForTesting();
|
||||
InputMethodServicesMonitor.getInstance().resetForTesting();
|
||||
}
|
||||
|
||||
synchronized void initialize(Context context, Index index) {
|
||||
if (mPackageManager != null) return;;
|
||||
mPackageManager = context.getPackageManager();
|
||||
|
||||
AccessibilityServicesMonitor.getInstance().initialize(context, index);
|
||||
InputMethodServicesMonitor.getInstance().initialize(context, index);
|
||||
|
||||
// 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.
|
||||
register(context, null /* thread */, UserHandle.CURRENT, false);
|
||||
}
|
||||
|
||||
// Covers installed, appeared external storage with the package, upgraded.
|
||||
@Override
|
||||
public void onPackageAppeared(String packageName, int uid) {
|
||||
postPackageAvailable(packageName);
|
||||
}
|
||||
|
||||
// Covers uninstalled, removed external storage with the package.
|
||||
@Override
|
||||
public void onPackageDisappeared(String packageName, int uid) {
|
||||
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(() -> {
|
||||
AccessibilityServicesMonitor.getInstance().onPackageAvailable(packageName);
|
||||
InputMethodServicesMonitor.getInstance().onPackageAvailable(packageName);
|
||||
}, DELAY_PROCESS_PACKAGE_CHANGE);
|
||||
}
|
||||
|
||||
private void postPackageUnavailable(final String packageName) {
|
||||
getRegisteredHandler().postDelayed(() -> {
|
||||
AccessibilityServicesMonitor.getInstance().onPackageUnavailable(packageName);
|
||||
InputMethodServicesMonitor.getInstance().onPackageUnavailable(packageName);
|
||||
}, DELAY_PROCESS_PACKAGE_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
// A singleton that holds list of available accessibility services and updates search index.
|
||||
private static class AccessibilityServicesMonitor {
|
||||
|
||||
// Null if not initialized.
|
||||
@Nullable private Index mIndex;
|
||||
private PackageManager mPackageManager;
|
||||
private final List<String> mAccessibilityServices = new ArrayList<>();
|
||||
|
||||
private AccessibilityServicesMonitor() {}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final AccessibilityServicesMonitor INSTANCE =
|
||||
new AccessibilityServicesMonitor();
|
||||
}
|
||||
|
||||
static AccessibilityServicesMonitor getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
synchronized void resetForTesting() {
|
||||
mIndex = null;
|
||||
}
|
||||
|
||||
synchronized void initialize(Context context, Index index) {
|
||||
if (mIndex != null) return;
|
||||
mIndex = index;
|
||||
mPackageManager = context.getPackageManager();
|
||||
mAccessibilityServices.clear();
|
||||
buildIndex(true /* rebuild */);
|
||||
|
||||
// Cache accessibility service packages to know when they go away.
|
||||
AccessibilityManager accessibilityManager = (AccessibilityManager) context
|
||||
.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
for (final AccessibilityServiceInfo accessibilityService
|
||||
: accessibilityManager.getInstalledAccessibilityServiceList()) {
|
||||
ResolveInfo resolveInfo = accessibilityService.getResolveInfo();
|
||||
if (resolveInfo != null && resolveInfo.serviceInfo != null) {
|
||||
mAccessibilityServices.add(resolveInfo.serviceInfo.packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void buildIndex(boolean rebuild) {
|
||||
mIndex.updateFromClassNameResource(AccessibilitySettings.class.getName(),
|
||||
rebuild, true /* includeInSearchResult */);
|
||||
}
|
||||
|
||||
synchronized void onPackageAvailable(String packageName) {
|
||||
if (mIndex == null) return;
|
||||
if (mAccessibilityServices.contains(packageName)) return;
|
||||
|
||||
final Intent intent = getAccessibilityServiceIntent(packageName);
|
||||
final List<ResolveInfo> services = mPackageManager
|
||||
.queryIntentServices(intent, 0 /* flags */);
|
||||
if (services == null || services.isEmpty()) return;
|
||||
mAccessibilityServices.add(packageName);
|
||||
buildIndex(false /* rebuild */);
|
||||
}
|
||||
|
||||
synchronized void onPackageUnavailable(String packageName) {
|
||||
if (mIndex == null) return;
|
||||
if (!mAccessibilityServices.remove(packageName)) return;
|
||||
buildIndex(true /* rebuild */);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
// Null if not initialized.
|
||||
@Nullable private Index mIndex;
|
||||
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 (mIndex != null) {
|
||||
mContentResolver.unregisterContentObserver(this /* observer */);
|
||||
}
|
||||
mIndex = null;
|
||||
}
|
||||
|
||||
synchronized void initialize(Context context, Index index) {
|
||||
final boolean hasFeatureIme = context.getPackageManager()
|
||||
.hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS);
|
||||
if (!hasFeatureIme) return;
|
||||
|
||||
if (mIndex != null) return;
|
||||
mIndex = index;
|
||||
mPackageManager = context.getPackageManager();
|
||||
mContentResolver = context.getContentResolver();
|
||||
mInputMethodServices.clear();
|
||||
buildIndex(InputMethodAndLanguageSettings.class, true /* rebuild */);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for related content URIs.
|
||||
mContentResolver.registerContentObserver(UserDictionary.Words.CONTENT_URI,
|
||||
true /* notifyForDescendants */, this /* observer */);
|
||||
// TODO: Should monitor android.provider.Settings.Secure.ENABLED_INPUT_METHODS and
|
||||
// update index of AvailableVirtualKeyboardFragment and VirtualKeyboardFragment.
|
||||
}
|
||||
|
||||
private void buildIndex(Class<?> indexClass, boolean rebuild) {
|
||||
mIndex.updateFromClassNameResource(indexClass.getName(), rebuild,
|
||||
true /* includeInSearchResult */);
|
||||
}
|
||||
|
||||
synchronized void onPackageAvailable(String packageName) {
|
||||
if (mIndex == 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);
|
||||
// TODO: Fix landing page to VirtualKeyboardFragment.
|
||||
buildIndex(InputMethodAndLanguageSettings.class, false /* rebuild */);
|
||||
}
|
||||
|
||||
synchronized void onPackageUnavailable(String packageName) {
|
||||
if (mIndex == null) return;
|
||||
if (!mInputMethodServices.remove(packageName)) return;
|
||||
// TODO: Fix landing page to AvailableVirtualKeyboardFragment.
|
||||
buildIndex(InputMethodAndLanguageSettings.class, true /* rebuild */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
if (UserDictionary.Words.CONTENT_URI.equals(uri)) {
|
||||
Index.getInstance(mContext).updateFromClassNameResource(
|
||||
InputMethodAndLanguageSettings.class.getName(), true, true);
|
||||
buildIndex(InputMethodAndLanguageSettings.class, true /* rebuild */);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user