VendorPolicyLoader.java revision 36d3dfaeac1a80561ef41a78ecc9bf3583d0ca7b
1/*
2 * Copyright (C) 2010 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
17package com.android.email;
18
19import com.android.email.activity.setup.AccountSettingsUtils.Provider;
20
21import android.content.Context;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.os.Bundle;
25import android.util.Log;
26
27import java.lang.reflect.Method;
28import java.net.URI;
29import java.net.URISyntaxException;
30
31/**
32 * A bridge class to the email vendor policy apk.
33 *
34 * <p>Email vendor policy is a system apk named "com.android.email.helper".  When exists, it must
35 * contain a class called "com.android.email.policy.EmailPolicy" with a static public method
36 * <code>Bundle getPolicy(String, Bundle)</code>, which serves vendor specific configurations.
37 *
38 * <p>A vendor policy apk is optional.  The email application will operate properly when none is
39 * found.
40 */
41public class VendorPolicyLoader {
42    private static final String POLICY_PACKAGE = "com.android.email.policy";
43    private static final String POLICY_CLASS = POLICY_PACKAGE + ".EmailPolicy";
44    private static final String GET_POLICY_METHOD = "getPolicy";
45    private static final Class<?>[] ARGS = new Class<?>[] {String.class, Bundle.class};
46
47    // call keys and i/o bundle keys
48    // when there is only one parameter or return value, use call key
49    private static final String USE_ALTERNATE_EXCHANGE_STRINGS = "useAlternateExchangeStrings";
50    private static final String GET_IMAP_ID = "getImapId";
51    private static final String GET_IMAP_ID_USER = "getImapId.user";
52    private static final String GET_IMAP_ID_HOST = "getImapId.host";
53    private static final String GET_IMAP_ID_CAPA = "getImapId.capabilities";
54    private static final String FIND_PROVIDER = "findProvider";
55    private static final String FIND_PROVIDER_IN_URI = "findProvider.inUri";
56    private static final String FIND_PROVIDER_IN_USER = "findProvider.inUser";
57    private static final String FIND_PROVIDER_OUT_URI = "findProvider.outUri";
58    private static final String FIND_PROVIDER_OUT_USER = "findProvider.outUser";
59    private static final String FIND_PROVIDER_NOTE = "findProvider.note";
60
61    /** Singleton instance */
62    private static VendorPolicyLoader sInstance;
63
64    private final Method mPolicyMethod;
65
66    public static VendorPolicyLoader getInstance(Context context) {
67        if (sInstance == null) {
68            // It's okay to instantiate VendorPolicyLoader multiple times.  No need to synchronize.
69            sInstance = new VendorPolicyLoader(context);
70        }
71        return sInstance;
72    }
73
74    /**
75     * For testing only.
76     *
77     * Replaces the instance with a new instance that loads a specified class.
78     */
79    public static void injectPolicyForTest(Context context, String apkPackageName, Class<?> clazz) {
80        String name = clazz.getName();
81        Log.d(Email.LOG_TAG, String.format("Using policy: package=%s name=%s",
82                apkPackageName, name));
83        sInstance = new VendorPolicyLoader(context, apkPackageName, name, true);
84    }
85
86    /**
87     * For testing only.
88     *
89     * Clear the instance so that the next {@link #getInstance} call will return a regular,
90     * non-injected instance.
91     */
92    public static void clearInstanceForTest() {
93        sInstance = null;
94    }
95
96    private VendorPolicyLoader(Context context) {
97        this(context, POLICY_PACKAGE, POLICY_CLASS, false);
98    }
99
100    /**
101     * Constructor for testing, where we need to use an alternate package/class name, and skip
102     * the system apk check.
103     */
104    /* package */ VendorPolicyLoader(Context context, String apkPackageName, String className,
105            boolean allowNonSystemApk) {
106        if (!allowNonSystemApk && !isSystemPackage(context, apkPackageName)) {
107            mPolicyMethod = null;
108            return;
109        }
110
111        Class<?> clazz = null;
112        Method method = null;
113        try {
114            final Context policyContext = context.createPackageContext(apkPackageName,
115                    Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
116            final ClassLoader classLoader = policyContext.getClassLoader();
117            clazz = classLoader.loadClass(className);
118            method = clazz.getMethod(GET_POLICY_METHOD, ARGS);
119        } catch (NameNotFoundException ignore) {
120            // Package not found -- it's okay - there's no policy .apk found, which is OK
121        } catch (ClassNotFoundException e) {
122            // Class not found -- probably not OK, but let's not crash here
123            Log.w(Email.LOG_TAG, "VendorPolicyLoader: " + e);
124        } catch (NoSuchMethodException e) {
125            // Method not found -- probably not OK, but let's not crash here
126            Log.w(Email.LOG_TAG, "VendorPolicyLoader: " + e);
127        }
128        mPolicyMethod = method;
129    }
130
131    // Not private for testing
132    /* package */ static boolean isSystemPackage(Context context, String packageName) {
133        try {
134            ApplicationInfo ai = context.getPackageManager().getApplicationInfo(packageName, 0);
135            return (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
136        } catch (NameNotFoundException e) {
137            return false; // Package not found.
138        }
139    }
140
141    /**
142     * Calls the getPolicy method in the policy apk, if one exists.  This method never returns null;
143     * It returns an empty {@link Bundle} when there is no policy apk (or even if the inner
144     * getPolicy returns null).
145     */
146    // Not private for testing
147    /* package */ Bundle getPolicy(String policy, Bundle args) {
148        Bundle ret = null;
149        if (mPolicyMethod != null) {
150            try {
151                ret = (Bundle) mPolicyMethod.invoke(null, policy, args);
152            } catch (Exception e) {
153                Log.w(Email.LOG_TAG, "VendorPolicyLoader", e);
154            }
155        }
156        return (ret != null) ? ret : Bundle.EMPTY;
157    }
158
159    /**
160     * Returns true if alternate exchange descriptive text is required.
161     *
162     * Vendor function:
163     *  Select: USE_ALTERNATE_EXCHANGE_STRINGS
164     *  Params: none
165     *  Result: USE_ALTERNATE_EXCHANGE_STRINGS (boolean)
166     */
167    public boolean useAlternateExchangeStrings() {
168        return getPolicy(USE_ALTERNATE_EXCHANGE_STRINGS, null)
169                .getBoolean(USE_ALTERNATE_EXCHANGE_STRINGS, false);
170    }
171
172    /**
173     * Returns additional key/value pairs for the IMAP ID string.
174     *
175     * Vendor function:
176     *  Select: GET_IMAP_ID
177     *  Params: GET_IMAP_ID_USER (String)
178     *          GET_IMAP_ID_HOST (String)
179     *          GET_IMAP_ID_CAPABILITIES (String)
180     *  Result: GET_IMAP_ID (String)
181     *
182     * @param userName the server that is being contacted (e.g. "imap.server.com")
183     * @param host the server that is being contacted (e.g. "imap.server.com")
184     * @param capabilities reported capabilities, if known.  null is OK
185     * @return zero or more key/value pairs, quoted and delimited by spaces.  If there is
186     * nothing to add, return null.
187     */
188    public String getImapIdValues(String userName, String host, String capabilities) {
189        Bundle params = new Bundle();
190        params.putString(GET_IMAP_ID_USER, userName);
191        params.putString(GET_IMAP_ID_HOST, host);
192        params.putString(GET_IMAP_ID_CAPA, capabilities);
193        String result = getPolicy(GET_IMAP_ID, params).getString(GET_IMAP_ID);
194        return result;
195    }
196
197    /**
198     * Returns provider setup information for a given email address
199     *
200     * Vendor function:
201     *  Select: FIND_PROVIDER
202     *  Param:  FIND_PROVIDER (String)
203     *  Result: FIND_PROVIDER_IN_URI
204     *          FIND_PROVIDER_IN_USER
205     *          FIND_PROVIDER_OUT_URI
206     *          FIND_PROVIDER_OUT_USER
207     *          FIND_PROVIDER_NOTE (optional - null is OK)
208     *
209     * Note, if we get this far, we expect "correct" results from the policy method.  But throwing
210     * checked exceptions requires a bunch of upstream changes, so we're going to catch them here
211     * and add logging.  Other exceptions may escape here (such as null pointers) to fail fast.
212     *
213     * @param domain The domain portion of the user's email address
214     * @return suitable Provider definition, or null if no match found
215     */
216    public Provider findProviderForDomain(String domain) {
217        Bundle params = new Bundle();
218        params.putString(FIND_PROVIDER, domain);
219        Bundle out = getPolicy(FIND_PROVIDER, params);
220        if (out != null && !out.isEmpty()) {
221            try {
222                Provider p = new Provider();
223                p.id = null;
224                p.label = null;
225                p.domain = domain;
226                p.incomingUriTemplate = new URI(out.getString(FIND_PROVIDER_IN_URI));
227                p.incomingUsernameTemplate = out.getString(FIND_PROVIDER_IN_USER);
228                p.outgoingUriTemplate = new URI(out.getString(FIND_PROVIDER_OUT_URI));
229                p.outgoingUsernameTemplate = out.getString(FIND_PROVIDER_OUT_USER);
230                p.note = out.getString(FIND_PROVIDER_NOTE);
231                return p;
232            } catch (URISyntaxException e) {
233                Log.d(Email.LOG_TAG, "uri exception while vendor policy loads " + domain);
234            }
235        }
236        return null;
237    }
238}
239