1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.search;
18 
19 import android.accessibilityservice.AccessibilityService;
20 import android.accessibilityservice.AccessibilityServiceInfo;
21 import android.app.Activity;
22 import android.app.LoaderManager;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.Loader;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.pm.ServiceInfo;
29 import android.database.ContentObserver;
30 import android.hardware.input.InputManager;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.Message;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.print.PrintManager;
39 import android.print.PrintServicesLoader;
40 import android.printservice.PrintServiceInfo;
41 import android.provider.UserDictionary;
42 import android.util.Log;
43 import android.view.accessibility.AccessibilityManager;
44 import android.view.inputmethod.InputMethodInfo;
45 import android.view.inputmethod.InputMethodManager;
46 
47 import com.android.internal.content.PackageMonitor;
48 import com.android.settings.accessibility.AccessibilitySettings;
49 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
50 import com.android.settings.print.PrintSettingsFragment;
51 
52 import java.util.ArrayList;
53 import java.util.List;
54 
55 public final class DynamicIndexableContentMonitor extends PackageMonitor implements
56         InputManager.InputDeviceListener,
57         LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
58     private static final String TAG = "DynamicIndexableContentMonitor";
59 
60     private static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000;
61 
62     private static final int MSG_PACKAGE_AVAILABLE = 1;
63     private static final int MSG_PACKAGE_UNAVAILABLE = 2;
64 
65     private final List<String> mAccessibilityServices = new ArrayList<String>();
66     private final List<String> mImeServices = new ArrayList<String>();
67 
68     private final Handler mHandler = new Handler() {
69         @Override
70         public void handleMessage(Message msg) {
71             switch (msg.what) {
72                 case MSG_PACKAGE_AVAILABLE: {
73                     String packageName = (String) msg.obj;
74                     handlePackageAvailable(packageName);
75                 } break;
76 
77                 case MSG_PACKAGE_UNAVAILABLE: {
78                     String packageName = (String) msg.obj;
79                     handlePackageUnavailable(packageName);
80                 } break;
81             }
82         }
83     };
84 
85     private final ContentObserver mUserDictionaryContentObserver =
86             new UserDictionaryContentObserver(mHandler);
87 
88     private Context mContext;
89     private boolean mHasFeatureIme;
90     private boolean mRegistered;
91 
getAccessibilityServiceIntent(String packageName)92     private static Intent getAccessibilityServiceIntent(String packageName) {
93         final Intent intent = new Intent(AccessibilityService.SERVICE_INTERFACE);
94         intent.setPackage(packageName);
95         return intent;
96     }
97 
getIMEServiceIntent(String packageName)98     private static Intent getIMEServiceIntent(String packageName) {
99         final Intent intent = new Intent("android.view.InputMethod");
100         intent.setPackage(packageName);
101         return intent;
102     }
103 
register(Activity activity, int loaderId)104     public void register(Activity activity, int loaderId) {
105         mContext = activity;
106 
107         if (!mContext.getSystemService(UserManager.class).isUserUnlocked()) {
108             Log.w(TAG, "Skipping content monitoring because user is locked");
109             mRegistered = false;
110             return;
111         } else {
112             mRegistered = true;
113         }
114 
115         boolean hasFeaturePrinting = mContext.getPackageManager().hasSystemFeature(
116                 PackageManager.FEATURE_PRINTING);
117         mHasFeatureIme = mContext.getPackageManager().hasSystemFeature(
118                 PackageManager.FEATURE_INPUT_METHODS);
119 
120         // Cache accessibility service packages to know when they go away.
121         AccessibilityManager accessibilityManager = (AccessibilityManager)
122                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
123         List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
124                 .getInstalledAccessibilityServiceList();
125         final int accessibilityServiceCount = accessibilityServices.size();
126         for (int i = 0; i < accessibilityServiceCount; i++) {
127             AccessibilityServiceInfo accessibilityService = accessibilityServices.get(i);
128             ResolveInfo resolveInfo = accessibilityService.getResolveInfo();
129             if (resolveInfo == null || resolveInfo.serviceInfo == null) {
130                 continue;
131             }
132             mAccessibilityServices.add(resolveInfo.serviceInfo.packageName);
133         }
134 
135         if (hasFeaturePrinting) {
136             activity.getLoaderManager().initLoader(loaderId, null, this);
137         }
138 
139         // Cache IME service packages to know when they go away.
140         if (mHasFeatureIme) {
141             InputMethodManager imeManager = (InputMethodManager)
142                     mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
143             List<InputMethodInfo> inputMethods = imeManager.getInputMethodList();
144             final int inputMethodCount = inputMethods.size();
145             for (int i = 0; i < inputMethodCount; i++) {
146                 InputMethodInfo inputMethod = inputMethods.get(i);
147                 ServiceInfo serviceInfo = inputMethod.getServiceInfo();
148                 if (serviceInfo == null) continue;
149                 mImeServices.add(serviceInfo.packageName);
150             }
151 
152             // Watch for related content URIs.
153             mContext.getContentResolver().registerContentObserver(
154                     UserDictionary.Words.CONTENT_URI, true, mUserDictionaryContentObserver);
155         }
156 
157         // Watch for input device changes.
158         InputManager inputManager = (InputManager) activity.getSystemService(
159                 Context.INPUT_SERVICE);
160         inputManager.registerInputDeviceListener(this, mHandler);
161 
162         // Start tracking packages.
163         register(activity, Looper.getMainLooper(), UserHandle.CURRENT, false);
164     }
165 
166     @Override
unregister()167     public void unregister() {
168         if (!mRegistered) return;
169 
170         super.unregister();
171 
172         InputManager inputManager = (InputManager) mContext.getSystemService(
173                 Context.INPUT_SERVICE);
174         inputManager.unregisterInputDeviceListener(this);
175 
176         if (mHasFeatureIme) {
177             mContext.getContentResolver().unregisterContentObserver(
178                     mUserDictionaryContentObserver);
179         }
180 
181         mAccessibilityServices.clear();
182         mImeServices.clear();
183     }
184 
185     // Covers installed, appeared external storage with the package, upgraded.
186     @Override
onPackageAppeared(String packageName, int uid)187     public void onPackageAppeared(String packageName, int uid) {
188         postMessage(MSG_PACKAGE_AVAILABLE, packageName);
189     }
190 
191     // Covers uninstalled, removed external storage with the package.
192     @Override
onPackageDisappeared(String packageName, int uid)193     public void onPackageDisappeared(String packageName, int uid) {
194         postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
195     }
196 
197     // Covers enabled, disabled.
198     @Override
onPackageModified(String packageName)199     public void onPackageModified(String packageName) {
200         super.onPackageModified(packageName);
201         try {
202             final int state = mContext.getPackageManager().getApplicationEnabledSetting(
203                     packageName);
204             if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
205                     || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
206                 postMessage(MSG_PACKAGE_AVAILABLE, packageName);
207             } else {
208                 postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
209             }
210         } catch (IllegalArgumentException e) {
211             Log.e(TAG, "Package does not exist: " + packageName, e);
212         }
213     }
214 
215     @Override
onInputDeviceAdded(int deviceId)216     public void onInputDeviceAdded(int deviceId) {
217         Index.getInstance(mContext).updateFromClassNameResource(
218                 InputMethodAndLanguageSettings.class.getName(), false, true);
219     }
220 
221     @Override
onInputDeviceRemoved(int deviceId)222     public void onInputDeviceRemoved(int deviceId) {
223         onInputDeviceChanged(deviceId);
224     }
225 
226     @Override
onInputDeviceChanged(int deviceId)227     public void onInputDeviceChanged(int deviceId) {
228         Index.getInstance(mContext).updateFromClassNameResource(
229                 InputMethodAndLanguageSettings.class.getName(), true, true);
230     }
231 
postMessage(int what, String packageName)232     private void postMessage(int what, String packageName) {
233         Message message = mHandler.obtainMessage(what, packageName);
234         mHandler.sendMessageDelayed(message, DELAY_PROCESS_PACKAGE_CHANGE);
235     }
236 
handlePackageAvailable(String packageName)237     private void handlePackageAvailable(String packageName) {
238         if (!mAccessibilityServices.contains(packageName)) {
239             final Intent intent = getAccessibilityServiceIntent(packageName);
240             List<?> services = mContext.getPackageManager().queryIntentServices(intent, 0);
241             if (services != null && !services.isEmpty()) {
242                 mAccessibilityServices.add(packageName);
243                 Index.getInstance(mContext).updateFromClassNameResource(
244                         AccessibilitySettings.class.getName(), false, true);
245             }
246         }
247 
248         if (mHasFeatureIme) {
249             if (!mImeServices.contains(packageName)) {
250                 Intent intent = getIMEServiceIntent(packageName);
251                 List<?> services = mContext.getPackageManager().queryIntentServices(intent, 0);
252                 if (services != null && !services.isEmpty()) {
253                     mImeServices.add(packageName);
254                     Index.getInstance(mContext).updateFromClassNameResource(
255                             InputMethodAndLanguageSettings.class.getName(), false, true);
256                 }
257             }
258         }
259     }
260 
handlePackageUnavailable(String packageName)261     private void handlePackageUnavailable(String packageName) {
262         final int accessibilityIndex = mAccessibilityServices.indexOf(packageName);
263         if (accessibilityIndex >= 0) {
264             mAccessibilityServices.remove(accessibilityIndex);
265             Index.getInstance(mContext).updateFromClassNameResource(
266                     AccessibilitySettings.class.getName(), true, true);
267         }
268 
269         if (mHasFeatureIme) {
270             final int imeIndex = mImeServices.indexOf(packageName);
271             if (imeIndex >= 0) {
272                 mImeServices.remove(imeIndex);
273                 Index.getInstance(mContext).updateFromClassNameResource(
274                         InputMethodAndLanguageSettings.class.getName(), true, true);
275             }
276         }
277     }
278 
279     @Override
onCreateLoader(int id, Bundle args)280     public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
281         return new PrintServicesLoader(
282                 (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE), mContext,
283                 PrintManager.ALL_SERVICES);
284     }
285 
286     @Override
onLoadFinished(Loader<List<PrintServiceInfo>> loader, List<PrintServiceInfo> services)287     public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
288             List<PrintServiceInfo> services) {
289         Index.getInstance(mContext).updateFromClassNameResource(
290                 PrintSettingsFragment.class.getName(), false, true);
291     }
292 
293     @Override
onLoaderReset(Loader<List<PrintServiceInfo>> loader)294     public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
295         // nothing to do
296     }
297 
298     private final class UserDictionaryContentObserver extends ContentObserver {
299 
UserDictionaryContentObserver(Handler handler)300         public UserDictionaryContentObserver(Handler handler) {
301             super(handler);
302         }
303 
304         @Override
onChange(boolean selfChange, Uri uri)305         public void onChange(boolean selfChange, Uri uri) {
306             if (UserDictionary.Words.CONTENT_URI.equals(uri)) {
307                 Index.getInstance(mContext).updateFromClassNameResource(
308                         InputMethodAndLanguageSettings.class.getName(), true, true);
309             }
310         };
311     }
312 }
313