/* * 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.accessibilityservice.AccessibilityServiceInfo; import android.app.Activity; import android.app.LoaderManager; 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.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.util.Log; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.content.PackageMonitor; import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.print.PrintSettingsFragment; import java.util.ArrayList; import java.util.List; public final class DynamicIndexableContentMonitor extends PackageMonitor implements InputManager.InputDeviceListener, LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> { private static final String TAG = "DynamicIndexableContentMonitor"; 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); private Context mContext; private boolean mHasFeatureIme; private boolean mRegistered; private 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"); intent.setPackage(packageName); return intent; } public void register(Activity activity, int loaderId) { mContext = activity; if (!mContext.getSystemService(UserManager.class).isUserUnlocked()) { Log.w(TAG, "Skipping content monitoring because user is locked"); mRegistered = false; return; } else { mRegistered = true; } 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); } // Watch for input device changes. InputManager inputManager = (InputManager) activity.getSystemService( Context.INPUT_SERVICE); inputManager.registerInputDeviceListener(this, mHandler); // Start tracking packages. register(activity, Looper.getMainLooper(), UserHandle.CURRENT, false); } @Override public void unregister() { if (!mRegistered) 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); } } } @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) { Index.getInstance(mContext).updateFromClassNameResource( PrintSettingsFragment.class.getName(), false, true); } @Override public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { // nothing to do } private final class UserDictionaryContentObserver extends ContentObserver { public UserDictionaryContentObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange, Uri uri) { if (UserDictionary.Words.CONTENT_URI.equals(uri)) { Index.getInstance(mContext).updateFromClassNameResource( InputMethodAndLanguageSettings.class.getName(), true, true); } }; } }