• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 Myriad Group AG.
3  * Copyright (C) 2009 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6  * use this file except in compliance with the License. You may obtain a copy of
7  * the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14  * License for the specific language governing permissions and limitations under
15  * the License.
16  */
17 
18 package com.android.im.app;
19 
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Map;
23 
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.ServiceInfo;
32 import android.database.Cursor;
33 import android.database.sqlite.SQLiteFullException;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.android.im.plugin.ImConfigNames;
40 import com.android.im.plugin.ImPlugin;
41 import com.android.im.plugin.ImPluginConstants;
42 import com.android.im.plugin.ImPluginInfo;
43 import com.android.im.provider.Imps;
44 
45 public class ImPluginHelper {
46 
47     private static final String TAG = "ImPluginUtils";
48 
49     private Context mContext;
50     private ArrayList<ImPluginInfo> mPluginsInfo;
51     private ArrayList<ImPlugin> mPluginObjects;
52     private boolean mLoaded;
53 
54     private static ImPluginHelper sInstance;
getInstance(Context context)55     public static ImPluginHelper getInstance(Context context) {
56         if (sInstance == null) {
57             sInstance = new ImPluginHelper(context);
58         }
59         return sInstance;
60     }
61 
ImPluginHelper(Context context)62     private ImPluginHelper(Context context) {
63         mContext = context;
64         mPluginsInfo = new ArrayList<ImPluginInfo>();
65         mPluginObjects = new ArrayList<ImPlugin>();
66     }
67 
getPluginsInfo()68     public ArrayList<ImPluginInfo> getPluginsInfo() {
69         if (!mLoaded) {
70             loadAvaiablePlugins();
71         }
72         return mPluginsInfo;
73     }
74 
getPluginObjects()75     public ArrayList<ImPlugin> getPluginObjects() {
76         if (!mLoaded) {
77             loadAvaiablePlugins();
78         }
79         return mPluginObjects;
80     }
81 
loadAvaiablePlugins()82     public void loadAvaiablePlugins() {
83         if (mLoaded) {
84             return;
85         }
86 
87         PackageManager pm = mContext.getPackageManager();
88         List<ResolveInfo> plugins = pm.queryIntentServices(
89                 new Intent(ImPluginConstants.PLUGIN_ACTION_NAME), PackageManager.GET_META_DATA);
90         for (ResolveInfo info : plugins) {
91             Log.d(TAG, "Found plugin " + info);
92 
93             ServiceInfo serviceInfo = info.serviceInfo;
94             if (serviceInfo == null) {
95                 Log.e(TAG, "Ignore bad IM plugin: " + info);
96                 continue;
97             }
98             String providerName = null;
99             String providerFullName = null;
100             String signUpUrl = null;
101             Bundle metaData = serviceInfo.metaData;
102             if (metaData != null) {
103                 providerName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_NAME);
104                 providerFullName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_FULL_NAME);
105                 signUpUrl = metaData.getString(ImPluginConstants.METADATA_SIGN_UP_URL);
106             }
107             if (TextUtils.isEmpty(providerName) || TextUtils.isEmpty(providerFullName)) {
108                 Log.e(TAG, "Ignore bad IM plugin: " + info + ". Lack of required meta data");
109                 continue;
110             }
111 
112             if (isPluginDuplicated(providerName)) {
113                 Log.e(TAG, "Ignore duplicated IM plugin: " + info);
114                 continue;
115             }
116 
117             if (!serviceInfo.packageName.equals(mContext.getPackageName())) {
118                 Log.e(TAG, "Ignore plugin in package: " + serviceInfo.packageName);
119                 continue;
120             }
121             ImPluginInfo pluginInfo = new ImPluginInfo(providerName, serviceInfo.packageName,
122                     serviceInfo.name, serviceInfo.applicationInfo.sourceDir);
123 
124             ImPlugin plugin = loadPlugin(pluginInfo);
125             if (plugin == null) {
126                 Log.e(TAG, "Ignore bad IM plugin");
127                 continue;
128             }
129 
130             try {
131                 updateProviderDb(plugin, pluginInfo,providerFullName, signUpUrl);
132             } catch (SQLiteFullException e) {
133                 Log.e(TAG, "Storage full", e);
134                 return;
135             }
136             mPluginsInfo.add(pluginInfo);
137             mPluginObjects.add(plugin);
138         }
139         mLoaded = true;
140     }
141 
isPluginDuplicated(String providerName)142     private boolean isPluginDuplicated(String providerName) {
143         for (ImPluginInfo plugin : mPluginsInfo) {
144             if (plugin.mProviderName.equals(providerName)) {
145                 return true;
146             }
147         }
148         return false;
149     }
150 
loadPlugin(ImPluginInfo pluginInfo)151     private ImPlugin loadPlugin(ImPluginInfo pluginInfo) {
152         // XXX Load the plug-in implementation directly from the apk rather than
153         // binding to the service and call through IPC Binder API. This is much
154         // more effective since we don't need to start the service in other
155         // process. We can not run the plug-in service in the same process as a
156         // local service because that the interface is defined in a shared
157         // library in order to compile the plug-in separately. In this case, the
158         // interface will be loaded by two class loader separately and a
159         // ClassCastException will be thrown if we cast the binder to the
160         // interface.
161         ClassLoader loader = mContext.getClassLoader();
162         try {
163             Class<?> cls = loader.loadClass(pluginInfo.mClassName);
164             return (ImPlugin) cls.newInstance();
165         } catch (ClassNotFoundException e) {
166             Log.e(TAG, "Could not find plugin class", e);
167         } catch (IllegalAccessException e) {
168             Log.e(TAG, "Could not create plugin instance", e);
169         } catch (InstantiationException e) {
170             Log.e(TAG, "Could not create plugin instance", e);
171         } catch (SecurityException e) {
172             Log.e(TAG, "Could not load plugin", e);
173         } catch (IllegalArgumentException e) {
174             Log.e(TAG, "Could not load plugin", e);
175         }
176         return null;
177     }
178 
updateProviderDb(ImPlugin plugin, ImPluginInfo info, String providerFullName, String signUpUrl)179     private long updateProviderDb(ImPlugin plugin, ImPluginInfo info,
180             String providerFullName, String signUpUrl) {
181         Map<String, String> config = loadConfiguration(plugin, info);
182         if (config == null) {
183             return 0;
184         }
185 
186         long providerId = 0;
187         ContentResolver cr = mContext.getContentResolver();
188         String where = Imps.Provider.NAME + "=?";
189         String[] selectionArgs = new String[]{info.mProviderName};
190         Cursor c = cr.query(Imps.Provider.CONTENT_URI,
191                 null /* projection */,
192                 where,
193                 selectionArgs,
194                 null /* sort order */);
195 
196         boolean pluginChanged;
197         try {
198             if (c.moveToFirst()) {
199                 providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Provider._ID));
200                 pluginChanged = isPluginChanged(cr, providerId, config);
201                 if (pluginChanged) {
202                     // Update the full name, signup url and category each time when the plugin change
203                     // instead of specific version change because this is called only once.
204                     // It's ok to update them even the values are not changed.
205                     // Note that we don't update the provider name because it's used as
206                     // identifier at some place and the plugin should never change it.
207                     ContentValues values = new ContentValues(3);
208                     values.put(Imps.Provider.FULLNAME, providerFullName);
209                     values.put(Imps.Provider.SIGNUP_URL, signUpUrl);
210                     values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY);
211                     Uri uri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId);
212                     cr.update(uri, values, null, null);
213                 }
214             } else {
215                 ContentValues values = new ContentValues(3);
216                 values.put(Imps.Provider.NAME, info.mProviderName);
217                 values.put(Imps.Provider.FULLNAME, providerFullName);
218                 values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY);
219                 values.put(Imps.Provider.SIGNUP_URL, signUpUrl);
220 
221                 Uri result = cr.insert(Imps.Provider.CONTENT_URI, values);
222                 providerId = ContentUris.parseId(result);
223                 pluginChanged = true;
224             }
225         } finally {
226             if (c != null) {
227                 c.close();
228             }
229         }
230 
231         if (pluginChanged) {
232             // Remove all the old settings
233             cr.delete(ContentUris.withAppendedId(
234                     Imps.ProviderSettings.CONTENT_URI, providerId),
235                     null, /*where*/
236                     null /*selectionArgs*/);
237 
238             ContentValues[] settingValues = new ContentValues[config.size()];
239 
240             int index = 0;
241             for (Map.Entry<String, String> entry : config.entrySet()) {
242                 ContentValues settingValue = new ContentValues();
243                 settingValue.put(Imps.ProviderSettings.PROVIDER, providerId);
244                 settingValue.put(Imps.ProviderSettings.NAME, entry.getKey());
245                 settingValue.put(Imps.ProviderSettings.VALUE, entry.getValue());
246                 settingValues[index++] = settingValue;
247             }
248             cr.bulkInsert(Imps.ProviderSettings.CONTENT_URI, settingValues);
249         }
250 
251         return providerId;
252     }
253 
loadConfiguration(ImPlugin plugin, ImPluginInfo info)254     private Map<String, String> loadConfiguration(ImPlugin plugin,
255             ImPluginInfo info) {
256         Map<String, String> config = null;
257 
258             config = plugin.getProviderConfig();
259 
260         if (config != null) {
261             config.put(ImConfigNames.PLUGIN_PATH, info.mSrcPath);
262             config.put(ImConfigNames.PLUGIN_CLASS, info.mClassName);
263         }
264         return config;
265     }
266 
isPluginChanged(ContentResolver cr, long providerId, Map<String, String> config)267     private boolean isPluginChanged(ContentResolver cr, long providerId,
268             Map<String, String> config) {
269         String origVersion = Imps.ProviderSettings.getStringValue(cr, providerId,
270                 ImConfigNames.PLUGIN_VERSION);
271 
272         if (origVersion == null) {
273             return true;
274         }
275         String newVersion = config.get(ImConfigNames.PLUGIN_VERSION);
276         return !origVersion.equals(newVersion);
277     }
278 }
279