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