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