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 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 */ 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 */ 91 public static void clearInstanceForTest() { 92 sInstance = null; 93 } 94 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 */ 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 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 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 */ 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 */ 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 */ 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