Finish up IMAP ID implementation
* scrub all external strings to keep them compliant for IMAP protocol * move Build.MODEL to x-android-device-model * send x-android-mobile-net-operator * send AGUID * unit tests for above * retrieve providers from VendorPolicyLoader Bug: 2332183
This commit is contained in:
parent
b89bc81f54
commit
ecb1af8041
|
@ -21,6 +21,8 @@ import android.content.SharedPreferences;
|
|||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class Preferences {
|
||||
|
||||
// Preferences file
|
||||
|
@ -33,6 +35,7 @@ public class Preferences {
|
|||
private static final String ENABLE_SENSITIVE_LOGGING = "enableSensitiveLogging";
|
||||
private static final String ENABLE_EXCHANGE_LOGGING = "enableExchangeLogging";
|
||||
private static final String ENABLE_EXCHANGE_FILE_LOGGING = "enableExchangeFileLogging";
|
||||
private static final String DEVICE_UID = "deviceUID";
|
||||
|
||||
private static Preferences preferences;
|
||||
|
||||
|
@ -161,6 +164,20 @@ public class Preferences {
|
|||
return mSharedPreferences.getBoolean(ENABLE_EXCHANGE_FILE_LOGGING, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new "device UID". This is local to Email app only, to prevent possibility
|
||||
* of correlation with any other user activities in any other apps.
|
||||
* @return a persistent, unique ID
|
||||
*/
|
||||
public synchronized String getDeviceUID() {
|
||||
String result = mSharedPreferences.getString(DEVICE_UID, null);
|
||||
if (result == null) {
|
||||
result = UUID.randomUUID().toString();
|
||||
mSharedPreferences.edit().putString(DEVICE_UID, result).commit();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.activity.setup.AccountSetupBasics.Provider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
@ -23,6 +25,8 @@ import android.os.Bundle;
|
|||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* A bridge class to the email vendor policy apk.
|
||||
|
@ -40,7 +44,19 @@ public class VendorPolicyLoader {
|
|||
private static final String GET_POLICY_METHOD = "getPolicy";
|
||||
private static final Class<?>[] ARGS = new Class<?>[] {String.class, Bundle.class};
|
||||
|
||||
// call keys and i/o bundle keys
|
||||
// when there is only one parameter or return value, use call key
|
||||
private static final String USE_ALTERNATE_EXCHANGE_STRINGS = "useAlternateExchangeStrings";
|
||||
private static final String GET_IMAP_ID = "getImapId";
|
||||
private static final String GET_IMAP_ID_USER = "getImapId.user";
|
||||
private static final String GET_IMAP_ID_HOST = "getImapId.host";
|
||||
private static final String GET_IMAP_ID_CAPA = "getImapId.capabilities";
|
||||
private static final String FIND_PROVIDER = "findProvider";
|
||||
private static final String FIND_PROVIDER_IN_URI = "findProvider.inUri";
|
||||
private static final String FIND_PROVIDER_IN_USER = "findProvider.inUser";
|
||||
private static final String FIND_PROVIDER_OUT_URI = "findProvider.outUri";
|
||||
private static final String FIND_PROVIDER_OUT_USER = "findProvider.outUser";
|
||||
private static final String FIND_PROVIDER_NOTE = "findProvider.note";
|
||||
|
||||
/** Singleton instance */
|
||||
private static VendorPolicyLoader sInstance;
|
||||
|
@ -120,9 +136,77 @@ public class VendorPolicyLoader {
|
|||
|
||||
/**
|
||||
* Returns true if alternate exchange descriptive text is required.
|
||||
*
|
||||
* Vendor function:
|
||||
* Select: USE_ALTERNATE_EXCHANGE_STRINGS
|
||||
* Params: none
|
||||
* Result: USE_ALTERNATE_EXCHANGE_STRINGS (boolean)
|
||||
*/
|
||||
public boolean useAlternateExchangeStrings() {
|
||||
return getPolicy(USE_ALTERNATE_EXCHANGE_STRINGS, null)
|
||||
.getBoolean(USE_ALTERNATE_EXCHANGE_STRINGS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional key/value pairs for the IMAP ID string.
|
||||
*
|
||||
* Vendor function:
|
||||
* Select: GET_IMAP_ID
|
||||
* Params: GET_IMAP_ID_USER (String)
|
||||
* GET_IMAP_ID_HOST (String)
|
||||
* GET_IMAP_ID_CAPABILITIES (String)
|
||||
* Result: GET_IMAP_ID (String)
|
||||
*
|
||||
* @param userName the server that is being contacted (e.g. "imap.server.com")
|
||||
* @param host the server that is being contacted (e.g. "imap.server.com")
|
||||
* @param reported capabilities, if known. null is OK
|
||||
* @return zero or more key/value pairs, quoted and delimited by spaces. If there is
|
||||
* nothing to add, return null.
|
||||
*/
|
||||
public String getImapIdValues(String userName, String host, String capabilities) {
|
||||
Bundle params = new Bundle();
|
||||
params.putString(GET_IMAP_ID_USER, userName);
|
||||
params.putString(GET_IMAP_ID_HOST, host);
|
||||
params.putString(GET_IMAP_ID_CAPA, capabilities);
|
||||
String result = getPolicy(GET_IMAP_ID, params).getString(GET_IMAP_ID);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns provider setup information for a given email address
|
||||
*
|
||||
* Vendor function:
|
||||
* Select: FIND_PROVIDER
|
||||
* Param: FIND_PROVIDER (String)
|
||||
* Result: FIND_PROVIDER_IN_URI
|
||||
* FIND_PROVIDER_IN_USER
|
||||
* FIND_PROVIDER_OUT_URI
|
||||
* FIND_PROVIDER_OUT_USER
|
||||
* FIND_PROVIDER_NOTE
|
||||
*
|
||||
* @param domain The domain portion of the user's email address
|
||||
* @return suitable Provider definition, or null if no match found
|
||||
*/
|
||||
public Provider findProviderForDomain(String domain) {
|
||||
Bundle params = new Bundle();
|
||||
params.putString(FIND_PROVIDER, domain);
|
||||
Bundle out = getPolicy(FIND_PROVIDER, params);
|
||||
if (out != null) {
|
||||
try {
|
||||
Provider p = new Provider();
|
||||
p.id = null;
|
||||
p.label = null;
|
||||
p.domain = domain;
|
||||
p.incomingUriTemplate = new URI(out.getString(FIND_PROVIDER_IN_URI));
|
||||
p.incomingUsernameTemplate = out.getString(FIND_PROVIDER_IN_USER);
|
||||
p.outgoingUriTemplate = new URI(out.getString(FIND_PROVIDER_OUT_URI));
|
||||
p.outgoingUsernameTemplate = out.getString(FIND_PROVIDER_OUT_USER);
|
||||
p.note = out.getString(FIND_PROVIDER_NOTE);
|
||||
return p;
|
||||
} catch (URISyntaxException e) {
|
||||
Log.d(Email.LOG_TAG, "uri exception while vendor policy loads " + domain);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -534,8 +534,9 @@ public class AccountSetupBasics extends Activity
|
|||
|
||||
/**
|
||||
* Search the list of known Email providers looking for one that matches the user's email
|
||||
* domain. We look in providers_product.xml first, followed by the entries in
|
||||
* platform providers.xml. This provides a nominal override capability.
|
||||
* domain. We check for vendor supplied values first, then we look in providers_product.xml
|
||||
* first, finally by the entries in platform providers.xml. This provides a nominal override
|
||||
* capability.
|
||||
*
|
||||
* A match is defined as any provider entry for which the "domain" attribute matches.
|
||||
*
|
||||
|
@ -543,7 +544,10 @@ public class AccountSetupBasics extends Activity
|
|||
* @return suitable Provider definition, or null if no match found
|
||||
*/
|
||||
private Provider findProviderForDomain(String domain) {
|
||||
Provider p = findProviderForDomain(domain, R.xml.providers_product);
|
||||
Provider p = VendorPolicyLoader.getInstance(this).findProviderForDomain(domain);
|
||||
if (p == null) {
|
||||
p = findProviderForDomain(domain, R.xml.providers_product);
|
||||
}
|
||||
if (p == null) {
|
||||
p = findProviderForDomain(domain, R.xml.providers);
|
||||
}
|
||||
|
@ -597,23 +601,16 @@ public class AccountSetupBasics extends Activity
|
|||
return null;
|
||||
}
|
||||
|
||||
static class Provider implements Serializable {
|
||||
public static class Provider implements Serializable {
|
||||
private static final long serialVersionUID = 8511656164616538989L;
|
||||
|
||||
public String id;
|
||||
|
||||
public String label;
|
||||
|
||||
public String domain;
|
||||
|
||||
public URI incomingUriTemplate;
|
||||
|
||||
public String incomingUsernameTemplate;
|
||||
|
||||
public URI outgoingUriTemplate;
|
||||
|
||||
public String outgoingUsernameTemplate;
|
||||
|
||||
public String note;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
package com.android.email.mail.store;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.Preferences;
|
||||
import com.android.email.Utility;
|
||||
import com.android.email.VendorPolicyLoader;
|
||||
import com.android.email.codec.binary.Base64;
|
||||
import com.android.email.mail.AuthenticationFailedException;
|
||||
import com.android.email.mail.CertificateValidationException;
|
||||
import com.android.email.mail.FetchProfile;
|
||||
|
@ -43,6 +46,7 @@ import com.beetstra.jutf7.CharsetProvider;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Config;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -54,12 +58,15 @@ import java.net.URISyntaxException;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
|
@ -174,7 +181,7 @@ public class ImapStore extends Store {
|
|||
mModifiedUtf7Charset = new CharsetProvider().charsetForName("X-RFC-3501");
|
||||
|
||||
// Assign user-agent string (for RFC2971 ID command)
|
||||
String mUserAgent = getImapId(context);
|
||||
String mUserAgent = getImapId(context, mUsername, mRootTransport.getHost());
|
||||
if (mUserAgent != null) {
|
||||
mIdPhrase = "ID (" + mUserAgent + ")";
|
||||
} else if (DEBUG_FORCE_SEND_ID) {
|
||||
|
@ -202,61 +209,142 @@ public class ImapStore extends Store {
|
|||
* because some connections may append additional values.
|
||||
*
|
||||
* The following IMAP ID keys may be included:
|
||||
* name Android package name of the program
|
||||
* os "android"
|
||||
* os-version "version; model; build-id"
|
||||
* vendor Vendor of the client/server
|
||||
* name Android package name of the program
|
||||
* os "android"
|
||||
* os-version "version; model; build-id"
|
||||
* vendor Vendor of the client/server
|
||||
* x-android-device-model Model (only revealed if release build)
|
||||
* x-android-net-operator Mobile network operator (if known)
|
||||
* AGUID A device+account UID
|
||||
*
|
||||
* In addition, a vendor policy .apk can append key/value pairs.
|
||||
*
|
||||
* @param userName the username of the account
|
||||
* @param host the host (server) of the account
|
||||
* @return a String for use in an IMAP ID message.
|
||||
*/
|
||||
public String getImapId(Context context) {
|
||||
synchronized (Email.class) {
|
||||
public String getImapId(Context context, String userName, String host) {
|
||||
// The first section is global to all IMAP connections, and generates the fixed
|
||||
// values in any IMAP ID message
|
||||
synchronized (ImapStore.class) {
|
||||
if (sImapId == null) {
|
||||
// "name" "com.android.email"
|
||||
StringBuffer sb = new StringBuffer("\"name\" \"");
|
||||
sb.append(context.getPackageName());
|
||||
sb.append("\"");
|
||||
String networkOperator = TelephonyManager.getDefault().getNetworkOperatorName();
|
||||
if (networkOperator == null) networkOperator = "";
|
||||
|
||||
// "os" "android"
|
||||
sb.append(" \"os\" \"android\"");
|
||||
|
||||
// "os-version" "version; model; build-id"
|
||||
sb.append(" \"os-version\" \"");
|
||||
final String version = Build.VERSION.RELEASE;
|
||||
if (version.length() > 0) {
|
||||
sb.append(version);
|
||||
} else {
|
||||
// default to "1.0"
|
||||
sb.append("1.0");
|
||||
}
|
||||
// add the model (on release builds only)
|
||||
if ("REL".equals(Build.VERSION.CODENAME)) {
|
||||
final String model = Build.MODEL;
|
||||
if (model.length() > 0) {
|
||||
sb.append("; ");
|
||||
sb.append(model);
|
||||
}
|
||||
}
|
||||
// add the build ID or build #
|
||||
final String id = Build.ID;
|
||||
if (id.length() > 0) {
|
||||
sb.append("; ");
|
||||
sb.append(id);
|
||||
}
|
||||
sb.append("\"");
|
||||
|
||||
// "vendor" "the vendor"
|
||||
final String vendor = Build.MANUFACTURER;
|
||||
if (vendor.length() > 0) {
|
||||
sb.append(" \"vendor\" \"");
|
||||
sb.append(vendor);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
sImapId = sb.toString();
|
||||
sImapId = makeCommonImapId(context.getPackageName(), Build.VERSION.RELEASE,
|
||||
Build.VERSION.CODENAME, Build.MODEL, Build.ID, Build.MANUFACTURER,
|
||||
networkOperator);
|
||||
}
|
||||
}
|
||||
return sImapId;
|
||||
|
||||
// This section is per Store, and adds in a dynamic elements like UID's.
|
||||
// We don't cache the result of this work, because the caller does anyway.
|
||||
StringBuilder id = new StringBuilder(sImapId);
|
||||
|
||||
// Optionally add any vendor-supplied id keys
|
||||
String vendorId =
|
||||
VendorPolicyLoader.getInstance(context).getImapIdValues(userName, host, null);
|
||||
if (vendorId != null) {
|
||||
id.append(' ');
|
||||
id.append(vendorId);
|
||||
}
|
||||
|
||||
// Generate a UID that mixes a "stable" device UID with the email address
|
||||
try {
|
||||
String devUID = Preferences.getPreferences(context).getDeviceUID();
|
||||
MessageDigest messageDigest;
|
||||
messageDigest = MessageDigest.getInstance("SHA-1");
|
||||
messageDigest.update(userName.getBytes());
|
||||
messageDigest.update(devUID.getBytes());
|
||||
byte[] uid = messageDigest.digest();
|
||||
String hexUid = new String(new Base64().encode(uid));
|
||||
id.append(" \"AGUID\" \"");
|
||||
id.append(hexUid);
|
||||
id.append('\"');
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Email.LOG_TAG, "couldn't obtain SHA-1 hash for device UID");
|
||||
}
|
||||
return id.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that actually builds the static part of the IMAP ID string. This is
|
||||
* separated from getImapId for testability. There is no escaping or encoding in IMAP ID so
|
||||
* any rogue chars must be filtered here.
|
||||
*
|
||||
* @param packageName context.getPackageName()
|
||||
* @param version Build.VERSION.RELEASE
|
||||
* @param codeName Build.VERSION.CODENAME
|
||||
* @param model Build.MODEL
|
||||
* @param id Build.ID
|
||||
* @param vendor Build.MANUFACTURER
|
||||
* @param networkOperator TelephonyManager.getNetworkOperatorName()
|
||||
* @return the static (never changes) portion of the IMAP ID
|
||||
*/
|
||||
/* package */ String makeCommonImapId(String packageName, String version,
|
||||
String codeName, String model, String id, String vendor, String networkOperator) {
|
||||
|
||||
// Before building up IMAP ID string, pre-filter the input strings for "legal" chars
|
||||
// This is using a fairly arbitrary char set intended to pass through most reasonable
|
||||
// version, model, and vendor strings: a-z A-Z 0-9 - _ + = ; : . , / <space>
|
||||
// The most important thing is *not* to pass parens, quotes, or CRLF, which would break
|
||||
// the format of the IMAP ID list.
|
||||
Pattern p = Pattern.compile("[^a-zA-Z0-9-_\\+=;:\\.,/ ]");
|
||||
packageName = p.matcher(packageName).replaceAll("");
|
||||
version = p.matcher(version).replaceAll("");
|
||||
codeName = p.matcher(codeName).replaceAll("");
|
||||
model = p.matcher(model).replaceAll("");
|
||||
id = p.matcher(id).replaceAll("");
|
||||
vendor = p.matcher(vendor).replaceAll("");
|
||||
networkOperator = p.matcher(networkOperator).replaceAll("");
|
||||
|
||||
// "name" "com.android.email"
|
||||
StringBuffer sb = new StringBuffer("\"name\" \"");
|
||||
sb.append(packageName);
|
||||
sb.append("\"");
|
||||
|
||||
// "os" "android"
|
||||
sb.append(" \"os\" \"android\"");
|
||||
|
||||
// "os-version" "version; build-id"
|
||||
sb.append(" \"os-version\" \"");
|
||||
if (version.length() > 0) {
|
||||
sb.append(version);
|
||||
} else {
|
||||
// default to "1.0"
|
||||
sb.append("1.0");
|
||||
}
|
||||
// add the build ID or build #
|
||||
if (id.length() > 0) {
|
||||
sb.append("; ");
|
||||
sb.append(id);
|
||||
}
|
||||
sb.append("\"");
|
||||
|
||||
// "vendor" "the vendor"
|
||||
if (vendor.length() > 0) {
|
||||
sb.append(" \"vendor\" \"");
|
||||
sb.append(vendor);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
// "x-android-device-model" the device model (on release builds only)
|
||||
if ("REL".equals(codeName)) {
|
||||
if (model.length() > 0) {
|
||||
sb.append(" \"x-android-device-model\" \"");
|
||||
sb.append(model);
|
||||
sb.append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
// "x-android-mobile-net-operator" "name of network operator"
|
||||
if (networkOperator.length() > 0) {
|
||||
sb.append(" \"x-android-mobile-net-operator\" \"");
|
||||
sb.append(networkOperator);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ import android.content.Context;
|
|||
import android.os.Bundle;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class VendorPolicyLoaderTest extends AndroidTestCase {
|
||||
/**
|
||||
* Test for the case where the helper package doesn't exist.
|
||||
|
@ -86,7 +88,7 @@ public class VendorPolicyLoaderTest extends AndroidTestCase {
|
|||
assertNull(MockVendorPolicy.passedPolicy);
|
||||
}
|
||||
|
||||
public static class MockVendorPolicy {
|
||||
private static class MockVendorPolicy {
|
||||
public static String passedPolicy;
|
||||
public static Bundle passedBundle;
|
||||
public static Bundle mockResult;
|
||||
|
@ -97,4 +99,40 @@ public class VendorPolicyLoaderTest extends AndroidTestCase {
|
|||
return mockResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that any vendor policy that happens to be installed returns legal values
|
||||
* for getImapIdValues() per its API.
|
||||
*
|
||||
* Note, in most cases very little will happen in this test, because there is
|
||||
* no vendor policy package. Most of this test exists to test a vendor policy
|
||||
* package itself, to make sure that its API returns reasonable values.
|
||||
*/
|
||||
public void testGetImapIdValues() {
|
||||
VendorPolicyLoader pl = VendorPolicyLoader.getInstance(getContext());
|
||||
String id = pl.getImapIdValues("user-name", "server.yahoo.com",
|
||||
"IMAP4rev1 STARTTLS AUTH=GSSAPI");
|
||||
// null is a reasonable result
|
||||
if (id == null) return;
|
||||
|
||||
// if non-null, basic sanity checks on format
|
||||
assertEquals("\"", id.charAt(0));
|
||||
assertEquals("\"", id.charAt(id.length()-1));
|
||||
// see if we can break it up properly
|
||||
String[] elements = id.split("\"");
|
||||
assertEquals(0, elements.length % 4);
|
||||
for (int i = 0; i < elements.length; ) {
|
||||
// Because we split at quotes, we expect to find:
|
||||
// [i] = null or one or more spaces
|
||||
// [i+1] = key
|
||||
// [i+2] = one or more spaces
|
||||
// [i+3] = value
|
||||
// Here are some incomplete checks of the above
|
||||
assertTrue(elements[i] == null || elements[i].startsWith(" "));
|
||||
assertTrue(elements[i+1].charAt(0) != ' ');
|
||||
assertTrue(elements[i+2].startsWith(" "));
|
||||
assertTrue(elements[i+3].charAt(0) != ' ');
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package com.android.email.mail.store;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.mail.FetchProfile;
|
||||
import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Folder;
|
||||
|
@ -31,8 +30,8 @@ import com.android.email.mail.internet.MimeUtility;
|
|||
import com.android.email.mail.transport.MockTransport;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.MoreAsserts;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
@ -93,40 +92,121 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
|||
|
||||
/**
|
||||
* Test the generation of the IMAP ID keys
|
||||
*
|
||||
* Since this is build-specific, we mostly just ensure that the correct strings
|
||||
* are being generated, and non-empty, and (if possible) look for expected formatting.
|
||||
*/
|
||||
public void testImapId() {
|
||||
String id = mStore.getImapId(getContext());
|
||||
public void testImapIdBasic() {
|
||||
// First test looks at operation of the outer API - we don't control any of the
|
||||
// values; Just look for basic results.
|
||||
|
||||
// Strings we'll expect to find:
|
||||
// name Android package name of the program
|
||||
// os "android"
|
||||
// os-version "version; build-id"
|
||||
// vendor Vendor of the client/server
|
||||
// x-android-device-model Model (Optional, so not tested here)
|
||||
// x-android-net-operator Carrier (Unreliable, so not tested here)
|
||||
// AGUID A device+account UID
|
||||
String id = mStore.getImapId(getContext(), "user-name", "host-name");
|
||||
HashMap<String, String> map = tokenizeImapId(id);
|
||||
assertEquals(getContext().getPackageName(), map.get("name"));
|
||||
assertEquals("android", map.get("os"));
|
||||
assertNotNull(map.get("os-version"));
|
||||
assertNotNull(map.get("vendor"));
|
||||
assertNotNull(map.get("AGUID"));
|
||||
|
||||
// Next, use the inner API to confirm operation of a couple of
|
||||
// variants for release and non-release devices.
|
||||
|
||||
// simple API check - non-REL codename, non-empty version
|
||||
id = mStore.makeCommonImapId("packageName", "version", "codeName",
|
||||
"model", "id", "vendor", "network-operator");
|
||||
map = tokenizeImapId(id);
|
||||
assertEquals("packageName", map.get("name"));
|
||||
assertEquals("android", map.get("os"));
|
||||
assertEquals("version; id", map.get("os-version"));
|
||||
assertEquals("vendor", map.get("vendor"));
|
||||
assertEquals(null, map.get("x-android-device-model"));
|
||||
assertEquals("network-operator", map.get("x-android-mobile-net-operator"));
|
||||
assertEquals(null, map.get("AGUID"));
|
||||
|
||||
// simple API check - codename is REL, so use model name.
|
||||
// also test empty version => 1.0 and empty network operator
|
||||
id = mStore.makeCommonImapId("packageName", "", "REL",
|
||||
"model", "id", "vendor", "");
|
||||
map = tokenizeImapId(id);
|
||||
assertEquals("packageName", map.get("name"));
|
||||
assertEquals("android", map.get("os"));
|
||||
assertEquals("1.0; id", map.get("os-version"));
|
||||
assertEquals("vendor", map.get("vendor"));
|
||||
assertEquals("model", map.get("x-android-device-model"));
|
||||
assertEquals(null, map.get("x-android-mobile-net-operator"));
|
||||
assertEquals(null, map.get("AGUID"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of the internal generator for IMAP ID strings, specifically looking for proper
|
||||
* filtering of illegal values. This is required because we cannot necessarily trust
|
||||
* the external sources of some of this data (e.g. release labels).
|
||||
*
|
||||
* The (somewhat arbitrary) legal values are: a-z A-Z 0-9 - _ + = ; : . , / <space>
|
||||
* The most important goal of the filters is to keep out control chars, (, ), and "
|
||||
*/
|
||||
public void testImapIdFiltering() {
|
||||
String id = mStore.makeCommonImapId("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"0123456789", "codeName",
|
||||
"model", "-_+=;:.,// ",
|
||||
"v(e)n\"d\ro\nr", // look for bad chars stripped out, leaving OK chars
|
||||
"()\""); // look for bad chars stripped out, leaving nothing
|
||||
HashMap<String, String> map = tokenizeImapId(id);
|
||||
|
||||
assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", map.get("name"));
|
||||
assertEquals("0123456789; -_+=;:.,// ", map.get("os-version"));
|
||||
assertEquals("vendor", map.get("vendor"));
|
||||
assertNull(map.get("x-android-mobile-net-operator"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that IMAP ID uid's are per-username
|
||||
*/
|
||||
public void testImapIdDeviceId() throws MessagingException {
|
||||
ImapStore store1a = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
|
||||
getContext(), null);
|
||||
ImapStore store1b = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
|
||||
getContext(), null);
|
||||
ImapStore store2 = (ImapStore) ImapStore.newInstance("imap://user2:password@server:999",
|
||||
getContext(), null);
|
||||
|
||||
String id1a = mStore.getImapId(getContext(), "user1", "host-name");
|
||||
String id1b = mStore.getImapId(getContext(), "user1", "host-name");
|
||||
String id2 = mStore.getImapId(getContext(), "user2", "host-name");
|
||||
|
||||
String uid1a = tokenizeImapId(id1a).get("AGUID");
|
||||
String uid1b = tokenizeImapId(id1b).get("AGUID");
|
||||
String uid2 = tokenizeImapId(id2).get("AGUID");
|
||||
|
||||
assertEquals(uid1a, uid1b);
|
||||
MoreAsserts.assertNotEqual(uid1a, uid2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to break an IMAP ID string into keys & values
|
||||
* @param id the IMAP Id string (the part inside the parens)
|
||||
* @return a map of key/value pairs
|
||||
*/
|
||||
private HashMap<String, String> tokenizeImapId(String id) {
|
||||
// Instead of a true tokenizer, we'll use double-quote as the split.
|
||||
// We can's use " " because there may be spaces inside the values.
|
||||
String[] elements = id.split("\"");
|
||||
HashMap<String, String> map = new HashMap<String, String>();
|
||||
for (int i = 0; i < elements.length; ) {
|
||||
// Because we split at quotes, we expect to find:
|
||||
// [i] = null
|
||||
// [i] = null or one or more spaces
|
||||
// [i+1] = key
|
||||
// [i+2] = one or more spaces
|
||||
// [i+3] = value
|
||||
map.put(elements[i+1], elements[i+3]);
|
||||
i += 4;
|
||||
}
|
||||
|
||||
// Strings we'll expect to find:
|
||||
// name Android package name of the program
|
||||
// os "android"
|
||||
// os-version "version; model; build-id"
|
||||
// vendor Vendor of the client/server
|
||||
|
||||
String name = map.get("name");
|
||||
assertEquals(getContext().getPackageName(), name);
|
||||
String os = map.get("os");
|
||||
assertEquals("android", os);
|
||||
String osversion = map.get("os-version");
|
||||
assertNotNull(osversion);
|
||||
String vendor = map.get("vendor");
|
||||
assertNotNull(vendor);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue