Handle provisioning of EAS 14.0
* EAS 14.0 requires a different procedure for sending device
information than does EAS 14.1
* Sadly, this new procedure is fairly incompatible with the way
we create/provision accounts, as it requires us to finish
provisioning BEFORE sending device information and yet
treating the result as part of the provisioning process
* This is particularly clumsy for us, since we do not normally
finish provisioning during account validation (we do it after
the account has been created).
* The present CL includes some patches that enable EAS 14.0
provisioning to work properly, at least in the most common
cases; additional testing will be required to determine
whether we're covering all of the bases
Bug: 5494233
Change-Id: I99c290a7d2b49b600446b4a40339efaae1cb1aac
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 48fb6e4..169f398 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -72,6 +72,7 @@
import com.android.exchange.adapter.PingParser;
import com.android.exchange.adapter.ProvisionParser;
import com.android.exchange.adapter.Serializer;
+import com.android.exchange.adapter.SettingsParser;
import com.android.exchange.adapter.Tags;
import com.android.exchange.provider.GalResult;
import com.android.exchange.provider.MailboxUtilities;
@@ -388,6 +389,12 @@
Log.w(TAG, "No supported EAS versions: " + supportedVersions);
throw new MessagingException(MessagingException.PROTOCOL_VERSION_UNSUPPORTED);
} else {
+ // Debug code for testing EAS 14.0; disables support for EAS 14.1
+ // "adb shell setprop log.tag.Exchange14 VERBOSE"
+ if (ourVersion.equals(Eas.SUPPORTED_PROTOCOL_EX2010_SP1) &&
+ Log.isLoggable("Exchange14", Log.VERBOSE)) {
+ ourVersion = Eas.SUPPORTED_PROTOCOL_EX2010;
+ }
service.mProtocolVersion = ourVersion;
service.mProtocolVersionDouble = Eas.getProtocolVersionDouble(ourVersion);
Account account = service.mAccount;
@@ -553,12 +560,19 @@
resultCode = MessagingException.SECURITY_POLICIES_REQUIRED;
bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET,
pp.getPolicy());
+ if (mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+ mAccount.mSecuritySyncKey = pp.getSecuritySyncKey();
+ if (!sendSettings()) {
+ userLog("Denied access: ", CommandStatus.toString(status));
+ resultCode = MessagingException.ACCESS_DENIED;
+ }
+ }
} else
// If not, set the proper code (the account will not be created)
resultCode = MessagingException.SECURITY_POLICIES_UNSUPPORTED;
bundle.putStringArray(
EmailServiceProxy.VALIDATE_BUNDLE_UNSUPPORTED_POLICIES,
- pp.getUnsupportedPolicies());
+ ((pp == null) ? new String[0] : pp.getUnsupportedPolicies()));
} else if (CommandStatus.isDeniedAccess(status)) {
userLog("Denied access: ", CommandStatus.toString(status));
resultCode = MessagingException.ACCESS_DENIED;
@@ -1414,8 +1428,14 @@
} else if (SecurityPolicyDelegate.isActive(mContext, policy)) {
// See if the required policies are in force; if they are, acknowledge the policies
// to the server and get the final policy key
- String securitySyncKey = acknowledgeProvision(pp.getSecuritySyncKey(),
- PROVISION_STATUS_OK);
+ // NOTE: For EAS 14.0, we already have the acknowledgment in the ProvisionParser
+ String securitySyncKey;
+ if (mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+ securitySyncKey = pp.getSecuritySyncKey();
+ } else {
+ securitySyncKey = acknowledgeProvision(pp.getSecuritySyncKey(),
+ PROVISION_STATUS_OK);
+ }
if (securitySyncKey != null) {
// If attachment policies have changed, fix up any affected attachment records
if (oldPolicy != null) {
@@ -1453,8 +1473,8 @@
private ProvisionParser canProvision() throws IOException {
Serializer s = new Serializer();
s.start(Tags.PROVISION_PROVISION);
- if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
- // Send settings information in 14.0 and greater
+ if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) {
+ // Send settings information in 14.1 and greater
s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET);
s.data(Tags.SETTINGS_MODEL, Build.MODEL);
//s.data(Tags.SETTINGS_IMEI, "");
@@ -1479,7 +1499,16 @@
if (pp.parse()) {
// The PolicySet in the ProvisionParser will have the requirements for all KNOWN
// policies. If others are required, hasSupportablePolicySet will be false
- if (!pp.hasSupportablePolicySet()) {
+ if (pp.hasSupportablePolicySet() &&
+ mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+ // In EAS 14.0, we need the final security key in order to use the settings
+ // command
+ String policyKey = acknowledgeProvision(pp.getSecuritySyncKey(),
+ PROVISION_STATUS_OK);
+ if (policyKey != null) {
+ pp.setSecuritySyncKey(policyKey);
+ }
+ } else if (!pp.hasSupportablePolicySet()) {
// Try to acknowledge using the "partial" status (i.e. we can partially
// accommodate the required policies). The server will agree to this if the
// "allow non-provisionable devices" setting is enabled on the server
@@ -1497,6 +1526,7 @@
} finally {
resp.close();
}
+
// On failures, simply return null
return null;
}
@@ -1556,6 +1586,29 @@
return null;
}
+ private boolean sendSettings() throws IOException {
+ Serializer s = new Serializer();
+ s.start(Tags.SETTINGS_SETTINGS);
+ s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET);
+ s.data(Tags.SETTINGS_MODEL, Build.MODEL);
+ s.data(Tags.SETTINGS_OS, "Android " + Build.VERSION.RELEASE);
+ s.data(Tags.SETTINGS_USER_AGENT, USER_AGENT);
+ s.end().end().end().done(); // SETTINGS_SET, SETTINGS_DEVICE_INFORMATION, SETTINGS_SETTINGS
+ EasResponse resp = sendHttpClientPost("Settings", s.toByteArray());
+ try {
+ int code = resp.getStatus();
+ if (code == HttpStatus.SC_OK) {
+ InputStream is = resp.getInputStream();
+ SettingsParser sp = new SettingsParser(is, this);
+ return sp.parse();
+ }
+ } finally {
+ resp.close();
+ }
+ // On failures, simply return false
+ return false;
+ }
+
/**
* Translate exit status code to service status code (used in callbacks)
* @param exitStatus the service's exit status
diff --git a/src/com/android/exchange/adapter/ProvisionParser.java b/src/com/android/exchange/adapter/ProvisionParser.java
index 67c23d2..05a864c 100644
--- a/src/com/android/exchange/adapter/ProvisionParser.java
+++ b/src/com/android/exchange/adapter/ProvisionParser.java
@@ -61,6 +61,10 @@
return mSecuritySyncKey;
}
+ public void setSecuritySyncKey(String securitySyncKey) {
+ mSecuritySyncKey = securitySyncKey;
+ }
+
public boolean getRemoteWipe() {
return mRemoteWipe;
}
diff --git a/src/com/android/exchange/adapter/SettingsParser.java b/src/com/android/exchange/adapter/SettingsParser.java
new file mode 100644
index 0000000..2cbc753
--- /dev/null
+++ b/src/com/android/exchange/adapter/SettingsParser.java
@@ -0,0 +1,82 @@
+/* Copyright (C) 2011 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.exchange.adapter;
+
+import com.android.exchange.EasSyncService;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parse the result of a Settings command.
+ *
+ * We only send the Settings command in EAS 14.0 after sending a Provision command for the first
+ * time. parse() returns true in the normal case; false if access to the account is denied due
+ * to the actual settings (e.g. if a particular device type isn't allowed by the server)
+ */
+public class SettingsParser extends Parser {
+ private final EasSyncService mService;
+
+ public SettingsParser(InputStream in, EasSyncService service) throws IOException {
+ super(in);
+ mService = service;
+ }
+
+ @Override
+ public boolean parse() throws IOException {
+ boolean res = false;
+ if (nextTag(START_DOCUMENT) != Tags.SETTINGS_SETTINGS) {
+ throw new IOException();
+ }
+ while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
+ if (tag == Tags.SETTINGS_STATUS) {
+ int status = getValueInt();
+ mService.userLog("Settings status = ", status);
+ if (status == 1) {
+ res = true;
+ } else {
+ // Access denied = 3; others should never be seen
+ res = false;
+ }
+ } else if (tag == Tags.SETTINGS_DEVICE_INFORMATION) {
+ parseDeviceInformation();
+ } else {
+ skipTag();
+ }
+ }
+ return res;
+ }
+
+ public void parseDeviceInformation() throws IOException {
+ while (nextTag(Tags.SETTINGS_DEVICE_INFORMATION) != END) {
+ if (tag == Tags.SETTINGS_SET) {
+ parseSet();
+ } else {
+ skipTag();
+ }
+ }
+ }
+
+ public void parseSet() throws IOException {
+ while (nextTag(Tags.SETTINGS_SET) != END) {
+ if (tag == Tags.SETTINGS_STATUS) {
+ mService.userLog("Set status = ", getValueInt());
+ } else {
+ skipTag();
+ }
+ }
+ }
+}