FileDocCategorySizeDatePackage
FrontDoorPlugin.javaAPI DocAndroid 1.5 API14611Wed May 06 22:42:46 BST 2009com.android.im.app

FrontDoorPlugin.java

/*
 * Copyright (C) 2008 Esmertec AG.
 * Copyright (C) 2008 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.im.app;

import com.android.im.plugin.ImConfigNames;
import com.android.im.plugin.ImPluginConstants;

import android.app.Service;
import android.content.Intent;
import android.content.ContentUris;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.im.IImPlugin;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Bundle;
import android.provider.Im;
import android.util.Log;
import android.text.TextUtils;
import android.database.Cursor;
import android.net.Uri;

import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

import dalvik.system.PathClassLoader;


public class FrontDoorPlugin extends Service {
    private final static String TAG = ImApp.LOG_TAG;
    private final static boolean LOCAL_DEBUG = false;

    // database access constants for branding resource map cache table
    private final static String[] BRANDING_RESOURCE_MAP_CACHE_PROJECTION = {
        Im.BrandingResourceMapCache.PROVIDER_ID,
        Im.BrandingResourceMapCache.APP_RES_ID,
        Im.BrandingResourceMapCache.PLUGIN_RES_ID
    };
    private final static int BRANDING_RESOURCE_MAP_CACHE_PROVIDER_ID_COLUMN = 0;
    private final static int BRANDING_RESOURCE_MAP_CACHE_APP_RES_ID_COLUMN = 1;
    private final static int BRANDING_RESOURCE_MAP_CACHE_PLUGIN_RES_ID_COLUMN = 2;

    private ArrayList<String> mProviderNames;
    private HashMap<String, String> mPackageNames;
    private HashMap<String, Map<Integer, Integer>> mBrandingResources;

    @Override
    public IBinder onBind(Intent intent) {
        // temporary mappings
        HashMap<String, Long> providerNameToId = new HashMap<String, Long>();
        HashMap<Long, String> providerIdToName = new HashMap<Long, String>();
        HashMap<String, Class> classes = new HashMap<String, Class>();

        loadThirdPartyPlugins(providerNameToId, providerIdToName, classes);
        loadBrandingResources(providerNameToId, providerIdToName, classes);

        return mBinder;
    }

    private void loadThirdPartyPlugins(
            HashMap<String, Long> providerNameToId, HashMap<Long, String> providerIdToName,
            HashMap<String, Class> classes) {
        mProviderNames = new ArrayList<String>();
        mPackageNames = new HashMap<String, String>();

        PackageManager pm = getPackageManager();
        List<ResolveInfo> plugins = pm.queryIntentServices(
                new Intent(ImPluginConstants.PLUGIN_ACTION_NAME), PackageManager.GET_META_DATA);
        int numPlugins = plugins.size();

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            log("loadThirdPartyPlugins: # plugins found: " + numPlugins);
        }

        if (numPlugins == 0) {
            Log.e(TAG, "[IM.FrontDoorPlugin] no plugins found! bail...");
            return;
        }

        for (ResolveInfo info : plugins) {
            if (Log.isLoggable(TAG, Log.DEBUG)) log("loadThirdPartyPlugins: found plugin " + info);

            ServiceInfo serviceInfo = info.serviceInfo;
            if (serviceInfo == null) {
                Log.e(TAG, "[FrontDoorPlugin] loadThirdPartyPlugins: ignore bad plugin: " + info);
                continue;
            }

            String providerName = null;
            String providerFullName = null;
            String signUpUrl = null;
            Bundle metaData = serviceInfo.metaData;
            if (metaData != null) {
                providerName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_NAME);
                providerFullName =
                    metaData.getString(ImPluginConstants.METADATA_PROVIDER_FULL_NAME);
                signUpUrl = metaData.getString(ImPluginConstants.METADATA_SIGN_UP_URL);
            }
            if (TextUtils.isEmpty(providerName) || TextUtils.isEmpty(providerFullName)) {
                Log.e(TAG, "[FrontDoorPlugin] Ignore bad IM plugin: " + info +
                        ". Lack of required meta data");
                continue;
            }

            mProviderNames.add(providerName);
            mPackageNames.put(providerName, serviceInfo.packageName);

            String className = serviceInfo.name;
            String srcPath = serviceInfo.applicationInfo.sourceDir;
            Class pluginClass = loadClass(className, srcPath);
            if (pluginClass == null) {
                Log.e(TAG, "[FrontDoorPlugin] Can not load package for plugin " + providerName);
                continue;
            }
            classes.put(providerName, pluginClass);

            Map<String, String> config = loadProviderConfigFromPlugin(pluginClass);
            if (config == null) {
                Log.e(TAG, "[FrontDoorPlugin] Can not load config for plugin " + providerName);
                continue;
            }
            config.put(ImConfigNames.PLUGIN_PATH, srcPath);
            config.put(ImConfigNames.PLUGIN_CLASS, className);

            long providerId = DatabaseUtils.updateProviderDb(getContentResolver(),
                    providerName, providerFullName, signUpUrl, config);
            providerNameToId.put(providerName, providerId);
            providerIdToName.put(providerId, providerName);
        }
    }

    private void loadBrandingResources(
            HashMap<String, Long> providerNameToId, HashMap<Long, String> providerIdToName,
            HashMap<String, Class> classes) {
        mBrandingResources = new HashMap<String, Map<Integer, Integer>>();

        // first try load from cache
        loadBrandingResourcesFromCache(providerIdToName);

        // check and load any un-cached resources
        final ArrayList<ContentValues> valuesList = new ArrayList<ContentValues>();
        for (String provider : mProviderNames) {
            long providerId = providerNameToId.get(provider);
            if (!mBrandingResources.containsKey(provider)) {
                Map<Integer, Integer> resMap = loadBrandingResource(classes.get(provider));
                if (resMap != null) {
                    mBrandingResources.put(provider, resMap);
                    for (int appResId : resMap.keySet()) {
                        int pluginResId = resMap.get(appResId);

                        ContentValues values = new ContentValues();
                        values.put(Im.BrandingResourceMapCache.PROVIDER_ID, providerId);
                        values.put(Im.BrandingResourceMapCache.APP_RES_ID, appResId);
                        values.put(Im.BrandingResourceMapCache.PLUGIN_RES_ID, pluginResId);

                        valuesList.add(values);
                    }
                    Log.d(TAG, "Plugin " + provider + " not in cache, loaded and saved");
                }
            }
        }

        // save the changes to cache
        if (valuesList.size() > 0) {
            new Thread(new Runnable() {
                public void run() {
                    getContentResolver().bulkInsert(
                            Im.BrandingResourceMapCache.CONTENT_URI,
                            valuesList.toArray(new ContentValues[]{}));
                }
            }).start();
        }
    }

    /**
     * Try loading the branding resources from the database.
     * @param providerIdToName a map between provider ID and name.
     */
    private void loadBrandingResourcesFromCache(HashMap<Long, String> providerIdToName) {
        ContentResolver cr = getContentResolver();
        Cursor c = cr.query(
                Im.BrandingResourceMapCache.CONTENT_URI, /* URI */
                BRANDING_RESOURCE_MAP_CACHE_PROJECTION,  /* projection */
                null,                                    /* where */
                null,                                    /* where args */
                null                                     /* sort */);

        if (c != null) {
            try {
                while (c.moveToNext()) {
                    long providerId = c.getLong(BRANDING_RESOURCE_MAP_CACHE_PROVIDER_ID_COLUMN);
                    String provider = providerIdToName.get(providerId);
                    if (TextUtils.isEmpty(provider)) {
                        Log.e(TAG, "Empty provider name in branding resource map cache table.");
                        continue;
                    }
                    int appResId = c.getInt(BRANDING_RESOURCE_MAP_CACHE_APP_RES_ID_COLUMN);
                    int pluginResId = c.getInt(BRANDING_RESOURCE_MAP_CACHE_PLUGIN_RES_ID_COLUMN);

                    Map<Integer, Integer> resMap = mBrandingResources.get(provider);
                    if (resMap == null) {
                        resMap = new HashMap<Integer, Integer>();
                        mBrandingResources.put(provider, resMap);
                    }

                    resMap.put(appResId, pluginResId);
                }
            } finally {
                c.close();
            }
        } else {
            Log.e(TAG, "Query of branding resource map cache table returns empty cursor"); 
        }
    }

    /**
     * Load branding resources from one plugin.
     */
    private Map<Integer, Integer> loadBrandingResource(Class cls) {
        try {
            Method m = cls.getMethod("getResourceMap");
            // TODO: this would still cause a VM verifier exception to be thrown if.
            // the landing page Android.mk and AndroidManifest.xml don't include use-library for
            // "com.android.im.plugin". This is even with getCustomClassLoader() as the parent
            // class loader.
            return (Map)m.invoke(cls.newInstance(), new Object[]{});

        } catch (IllegalAccessException e) {
            Log.e(TAG, "Failed load the plugin resource map", e);
        } catch (InstantiationException e) {
            Log.e(TAG, "Failed load the plugin resource map", e);
        } catch (SecurityException e) {
            Log.e(TAG, "Failed load the plugin resource map", e);
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "Failed load the plugin resource map", e);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Failed load the plugin resource map", e);
        } catch (InvocationTargetException e) {
            Log.e(TAG, "Failed load the plugin resource map", e);
        }
        return null;
    }

    /**
     * Load plugin config.
     */
    private Map<String, String> loadProviderConfigFromPlugin(Class cls) {
        try {
            Method m = cls.getMethod("onBind", Intent.class);
            com.android.im.plugin.IImPlugin plugin =
                (com.android.im.plugin.IImPlugin)m.invoke(cls.newInstance(), new Object[]{null});
            return plugin.getProviderConfig();
        } catch (IllegalAccessException e) {
            Log.e(TAG, "Could not create plugin instance", e);
        } catch (InstantiationException e) {
            Log.e(TAG, "Could not create plugin instance", e);
        } catch (SecurityException e) {
            Log.e(TAG, "Could not load config from the plugin", e);
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "Could not load config from the plugin", e);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Could not load config from the plugin", e);
        } catch (InvocationTargetException e) {
            Log.e(TAG, "Could not load config from the plugin", e);
        } catch (RemoteException e) {
            Log.e(TAG, "Could not load config from the plugin", e);
        }
        return null;
    }

    private Class loadClass(String className, String srcPath) {
        PathClassLoader loader = new PathClassLoader(srcPath, getClassLoader());
        try {
            return loader.loadClass(className);
        } catch (ClassNotFoundException e) {
            Log.e(TAG, "Could not find plugin class", e);
        }
        return null;
    }

    private void log(String msg) {
        Log.d(TAG, "[ImFrontDoor] " + msg);
    }


    /**
     * The implementation of IImFrontDoorPlugin defined through AIDL.
     */
    private final IImPlugin.Stub mBinder = new IImPlugin.Stub() {

        /**
         * Notify the plugin the front door activity is created. This gives the plugin a chance to
         * start its own servics, etc.
         */
        public void onStart() {
        }

        /**
         * Notify the plugin the front door activity is stopping.
         */
        public void onStop() {
        }

        /**
         * Sign in to the service for the account passed in.
         */
        public void signIn(long account) {
            if (LOCAL_DEBUG) log("signIn for account " + account);

            Intent intent = new Intent();
            intent.setData(ContentUris.withAppendedId(Im.Account.CONTENT_URI, account));
            intent.setClassName("com.android.im", "com.android.im.app.SigningInActivity");

            startActivity(intent);
        }

        /**
         * Sign out of the service for the account passed in.
         */
        public void signOut(long account) {
            if (LOCAL_DEBUG) log("signOut for account " + account);
            Intent intent = new Intent();
            intent.setData(ContentUris.withAppendedId(Im.Account.CONTENT_URI, account));
            intent.setClassName("com.android.im", "com.android.im.app.SignoutActivity");

            startActivity(intent);
        }

        public String getResourcePackageNameForProvider(String providerName) {
            return mPackageNames.get(providerName);
        }

        public Map getResourceMapForProvider(String providerName) throws RemoteException {
            return mBrandingResources.get(providerName);
        }

        public List getSupportedProviders() {
            return mProviderNames;
        }
    };

}