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