From 9ba506c4dd498150555f6c59aa758f7467bf9236 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Tue, 8 Feb 2011 18:54:56 -0800 Subject: [PATCH] Email split, part deux: PolicyService * Split PolicySet from SecurityPolicy and move to emailcommon * Define PolicyService that sync adapter services can use to interact with the Email DPM administrator * Implement PolicyServiceProxy for exchange * Implement PolicyService in email * Modify imports, references, etc. as required Bug: 3442973 Change-Id: I92015e21f780a68754b318da89fbb33570f334a2 --- Android.mk | 3 +- AndroidManifest.xml | 12 + src/com/android/email/SecurityPolicy.java | 345 +---------------- .../setup/AccountCheckSettingsFragment.java | 2 +- .../activity/setup/AccountSetupOptions.java | 2 +- .../email/activity/setup/SetupData.java | 2 +- .../android/email/service/PolicyService.java | 74 ++++ .../emailcommon/service/IPolicyService.aidl | 30 ++ .../service/PolicyServiceProxy.java | 228 +++++++++++ .../emailcommon/service/PolicySet.aidl | 19 + .../emailcommon/service/PolicySet.java | 356 ++++++++++++++++++ src/com/android/exchange/EasSyncService.java | 24 +- .../exchange/adapter/ProvisionParser.java | 12 +- .../android/email/SecurityPolicyTests.java | 6 +- .../adapter/ProvisionParserTests.java | 2 +- 15 files changed, 757 insertions(+), 360 deletions(-) create mode 100644 src/com/android/email/service/PolicyService.java create mode 100644 src/com/android/emailcommon/service/IPolicyService.aidl create mode 100644 src/com/android/emailcommon/service/PolicyServiceProxy.java create mode 100644 src/com/android/emailcommon/service/PolicySet.aidl create mode 100644 src/com/android/emailcommon/service/PolicySet.java diff --git a/Android.mk b/Android.mk index 960ed4315..451c39b75 100644 --- a/Android.mk +++ b/Android.mk @@ -20,7 +20,8 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_SRC_FILES += \ src/com/android/emailcommon/service/IEmailService.aidl \ - src/com/android/emailcommon/service/IEmailServiceCallback.aidl + src/com/android/emailcommon/service/IEmailServiceCallback.aidl \ + src/com/android/emailcommon/service/IPolicyService.aidl LOCAL_STATIC_JAVA_LIBRARIES := android-common # Revive this when the app is unbundled. diff --git a/AndroidManifest.xml b/AndroidManifest.xml index cf8306b7c..f8b4b56f8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -358,6 +358,18 @@ android:resource="@xml/syncadapter_pop_imap" /> + + + + + + + wipe device (0=disabled) - private static final int PASSWORD_MAX_FAILS_SHIFT = 9; - private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT; - public static final int PASSWORD_MAX_FAILS_MAX = 31; - // bits 14..24: seconds to screen lock (0=not required) - private static final int SCREEN_LOCK_TIME_SHIFT = 14; - private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT; - public static final int SCREEN_LOCK_TIME_MAX = 2047; - // bit 25: remote wipe capability required - private static final int REQUIRE_REMOTE_WIPE = 1 << 25; - // bit 26..35: password expiration (days; 0=not required) - private static final int PASSWORD_EXPIRATION_SHIFT = 26; - private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT; - public static final int PASSWORD_EXPIRATION_MAX = 1023; - // bit 36..43: password history (length; 0=not required) - private static final int PASSWORD_HISTORY_SHIFT = 36; - private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT; - public static final int PASSWORD_HISTORY_MAX = 255; - // bit 44..48: min complex characters (0=not required) - private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44; - private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT; - public static final int PASSWORD_COMPLEX_CHARS_MAX = 31; - // bit 49: requires device encryption - private static final long REQUIRE_ENCRYPTION = 1L << 49; - - /* Convert days to mSec (used for password expiration) */ - private static final long DAYS_TO_MSEC = 24 * 60 * 60 * 1000; - /* Small offset (2 minutes) added to policy expiration to make user testing easier. */ - private static final long EXPIRATION_OFFSET_MSEC = 2 * 60 * 1000; - - /*package*/ final int mMinPasswordLength; - /*package*/ final int mPasswordMode; - /*package*/ final int mMaxPasswordFails; - /*package*/ final int mMaxScreenLockTime; - /*package*/ final boolean mRequireRemoteWipe; - /*package*/ final int mPasswordExpirationDays; - /*package*/ final int mPasswordHistory; - /*package*/ final int mPasswordComplexChars; - /*package*/ final boolean mRequireEncryption; - - public int getMinPasswordLengthForTest() { - return mMinPasswordLength; - } - - public int getPasswordModeForTest() { - return mPasswordMode; - } - - public int getMaxPasswordFailsForTest() { - return mMaxPasswordFails; - } - - public int getMaxScreenLockTimeForTest() { - return mMaxScreenLockTime; - } - - public boolean isRequireRemoteWipeForTest() { - return mRequireRemoteWipe; - } - - public boolean isRequireEncryptionForTest() { - return mRequireEncryption; - } - - /** - * Create from raw values. - * @param minPasswordLength (0=not enforced) - * @param passwordMode - * @param maxPasswordFails (0=not enforced) - * @param maxScreenLockTime in seconds (0=not enforced) - * @param requireRemoteWipe - * @param passwordExpirationDays in days (0=not enforced) - * @param passwordHistory (0=not enforced) - * @param passwordComplexChars (0=not enforced) - * @throws IllegalArgumentException for illegal arguments. - */ - public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails, - int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpirationDays, - int passwordHistory, int passwordComplexChars, boolean requireEncryption) - throws IllegalArgumentException { - // If we're not enforcing passwords, make sure we clean up related values, since EAS - // can send non-zero values for any or all of these - if (passwordMode == PASSWORD_MODE_NONE) { - maxPasswordFails = 0; - maxScreenLockTime = 0; - minPasswordLength = 0; - passwordComplexChars = 0; - passwordHistory = 0; - passwordExpirationDays = 0; - } else { - if ((passwordMode != PASSWORD_MODE_SIMPLE) && - (passwordMode != PASSWORD_MODE_STRONG)) { - throw new IllegalArgumentException("password mode"); - } - // If we're only requiring a simple password, set complex chars to zero; note - // that EAS can erroneously send non-zero values in this case - if (passwordMode == PASSWORD_MODE_SIMPLE) { - passwordComplexChars = 0; - } - // The next four values have hard limits which cannot be supported if exceeded. - if (minPasswordLength > PASSWORD_LENGTH_MAX) { - throw new IllegalArgumentException("password length"); - } - if (passwordExpirationDays > PASSWORD_EXPIRATION_MAX) { - throw new IllegalArgumentException("password expiration"); - } - if (passwordHistory > PASSWORD_HISTORY_MAX) { - throw new IllegalArgumentException("password history"); - } - if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) { - throw new IllegalArgumentException("complex chars"); - } - // This value can be reduced (which actually increases security) if necessary - if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) { - maxPasswordFails = PASSWORD_MAX_FAILS_MAX; - } - // This value can be reduced (which actually increases security) if necessary - if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) { - maxScreenLockTime = SCREEN_LOCK_TIME_MAX; - } - } - mMinPasswordLength = minPasswordLength; - mPasswordMode = passwordMode; - mMaxPasswordFails = maxPasswordFails; - mMaxScreenLockTime = maxScreenLockTime; - mRequireRemoteWipe = requireRemoteWipe; - mPasswordExpirationDays = passwordExpirationDays; - mPasswordHistory = passwordHistory; - mPasswordComplexChars = passwordComplexChars; - mRequireEncryption = requireEncryption; - } - - /** - * Create from values encoded in an account - * @param account - */ - public PolicySet(Account account) { - this(account.mSecurityFlags); - } - - /** - * Create from values encoded in an account flags int - */ - public PolicySet(long flags) { - mMinPasswordLength = - (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT); - mPasswordMode = - (int) (flags & PASSWORD_MODE_MASK); - mMaxPasswordFails = - (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT); - mMaxScreenLockTime = - (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT); - mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE); - mPasswordExpirationDays = - (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT); - mPasswordHistory = - (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT); - mPasswordComplexChars = - (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT); - mRequireEncryption = 0 != (flags & REQUIRE_ENCRYPTION); - } - - /** - * Helper to map our internal encoding to DevicePolicyManager password modes. - */ - public int getDPManagerPasswordQuality() { - switch (mPasswordMode) { - case PASSWORD_MODE_SIMPLE: - return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; - case PASSWORD_MODE_STRONG: - return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; - default: - return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED; - } - } - - /** - * Helper to map expiration times to the millisecond values used by DevicePolicyManager. - */ - public long getDPManagerPasswordExpirationTimeout() { - long result = mPasswordExpirationDays * DAYS_TO_MSEC; - // Add a small offset to the password expiration. This makes it easier to test - // by changing (for example) 1 day to 1 day + 5 minutes. If you set an expiration - // that is within the warning period, you should get a warning fairly quickly. - if (result > 0) { - result += EXPIRATION_OFFSET_MSEC; - } - return result; - } - - /** - * Record flags (and a sync key for the flags) into an Account - * Note: the hash code is defined as the encoding used in Account - * - * @param account to write the values mSecurityFlags and mSecuritySyncKey - * @param syncKey the value to write into the account's mSecuritySyncKey - * @param update if true, also writes the account back to the provider (updating only - * the fields changed by this API) - * @param context a context for writing to the provider - * @return true if the actual policies changed, false if no change (note, sync key - * does not affect this) - */ - public boolean writeAccount(Account account, String syncKey, boolean update, - Context context) { - long newFlags = getSecurityCode(); - boolean dirty = (newFlags != account.mSecurityFlags); - account.mSecurityFlags = newFlags; - account.mSecuritySyncKey = syncKey; - if (update) { - if (account.isSaved()) { - ContentValues cv = new ContentValues(); - cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags); - cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey); - account.update(context, cv); - } else { - account.save(context); - } - } - return dirty; - } - - @Override - public boolean equals(Object o) { - if (o instanceof PolicySet) { - PolicySet other = (PolicySet)o; - return (this.getSecurityCode() == other.getSecurityCode()); - } - return false; - } - - /** - * Supports Parcelable - */ - public int describeContents() { - return 0; - } - - /** - * Supports Parcelable - */ - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public PolicySet createFromParcel(Parcel in) { - return new PolicySet(in); - } - - public PolicySet[] newArray(int size) { - return new PolicySet[size]; - } - }; - - /** - * Supports Parcelable - */ - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mMinPasswordLength); - dest.writeInt(mPasswordMode); - dest.writeInt(mMaxPasswordFails); - dest.writeInt(mMaxScreenLockTime); - dest.writeInt(mRequireRemoteWipe ? 1 : 0); - dest.writeInt(mPasswordExpirationDays); - dest.writeInt(mPasswordHistory); - dest.writeInt(mPasswordComplexChars); - dest.writeInt(mRequireEncryption ? 1 : 0); - } - - /** - * Supports Parcelable - */ - public PolicySet(Parcel in) { - mMinPasswordLength = in.readInt(); - mPasswordMode = in.readInt(); - mMaxPasswordFails = in.readInt(); - mMaxScreenLockTime = in.readInt(); - mRequireRemoteWipe = in.readInt() == 1; - mPasswordExpirationDays = in.readInt(); - mPasswordHistory = in.readInt(); - mPasswordComplexChars = in.readInt(); - mRequireEncryption = in.readInt() == 1; - } - - @Override - public int hashCode() { - long code = getSecurityCode(); - return (int) code; - } - - public long getSecurityCode() { - long flags = 0; - flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT; - flags |= mPasswordMode; - flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT; - flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT; - if (mRequireRemoteWipe) flags |= REQUIRE_REMOTE_WIPE; - flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT; - flags |= (long)mPasswordExpirationDays << PASSWORD_EXPIRATION_SHIFT; - flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT; - if (mRequireEncryption) flags |= REQUIRE_ENCRYPTION; - return flags; - } - - @Override - public String toString() { - return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode - + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max=" - + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe - + " pw-expiration=" + mPasswordExpirationDays - + " pw-history=" + mPasswordHistory - + " pw-complex-chars=" + mPasswordComplexChars - + " require-encryption=" + mRequireEncryption + "}"; - } - } - /** * If we are not the active device admin, try to become so. * diff --git a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java index 6f5a47c36..84d115a10 100644 --- a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java +++ b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java @@ -19,13 +19,13 @@ package com.android.email.activity.setup; import com.android.email.Email; import com.android.email.R; import com.android.email.Utility; -import com.android.email.SecurityPolicy.PolicySet; import com.android.email.mail.MessagingException; import com.android.email.mail.Sender; import com.android.email.mail.Store; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.HostAuth; import com.android.emailcommon.service.EmailServiceProxy; +import com.android.emailcommon.service.PolicySet; import android.app.Activity; import android.app.AlertDialog; diff --git a/src/com/android/email/activity/setup/AccountSetupOptions.java b/src/com/android/email/activity/setup/AccountSetupOptions.java index 95b4fcb3a..f07d62263 100644 --- a/src/com/android/email/activity/setup/AccountSetupOptions.java +++ b/src/com/android/email/activity/setup/AccountSetupOptions.java @@ -19,13 +19,13 @@ package com.android.email.activity.setup; import com.android.email.Email; import com.android.email.ExchangeUtils; import com.android.email.R; -import com.android.email.SecurityPolicy.PolicySet; import com.android.email.Utility; import com.android.email.activity.ActivityHelper; import com.android.email.mail.Store; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent.Account; import com.android.email.service.MailService; +import com.android.emailcommon.service.PolicySet; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; diff --git a/src/com/android/email/activity/setup/SetupData.java b/src/com/android/email/activity/setup/SetupData.java index a3abfd245..4255126f7 100644 --- a/src/com/android/email/activity/setup/SetupData.java +++ b/src/com/android/email/activity/setup/SetupData.java @@ -16,8 +16,8 @@ package com.android.email.activity.setup; -import com.android.email.SecurityPolicy.PolicySet; import com.android.email.provider.EmailContent.Account; +import com.android.emailcommon.service.PolicySet; import android.accounts.AccountAuthenticatorResponse; import android.os.Bundle; diff --git a/src/com/android/email/service/PolicyService.java b/src/com/android/email/service/PolicyService.java new file mode 100644 index 000000000..ace65a716 --- /dev/null +++ b/src/com/android/email/service/PolicyService.java @@ -0,0 +1,74 @@ +/* + * 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.email.service; + +import com.android.email.SecurityPolicy; +import com.android.emailcommon.service.IPolicyService; +import com.android.emailcommon.service.PolicySet; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; + +public class PolicyService extends Service { + + private SecurityPolicy mSecurityPolicy; + private Context mContext; + + private final IPolicyService.Stub mBinder = new IPolicyService.Stub() { + public boolean isActive(PolicySet policies) { + return mSecurityPolicy.isActive(policies); + } + + public void policiesRequired(long accountId) { + mSecurityPolicy.policiesRequired(accountId); + } + + public void updatePolicies(long accountId) { + mSecurityPolicy.updatePolicies(accountId); + } + + public void setAccountHoldFlag(long accountId, boolean newState) { + SecurityPolicy.setAccountHoldFlag(mContext, accountId, newState); + } + + public boolean isActiveAdmin() { + return mSecurityPolicy.isActiveAdmin(); + } + + public void remoteWipe() { + mSecurityPolicy.remoteWipe(); + } + + public boolean isSupported(PolicySet policies) { + return mSecurityPolicy.isSupported(policies); + } + + public PolicySet clearUnsupportedPolicies(PolicySet policies) { + return mSecurityPolicy.clearUnsupportedPolicies(policies); + } + }; + + @Override + public IBinder onBind(Intent intent) { + // When we bind this service, save the context and SecurityPolicy singleton + mContext = this; + mSecurityPolicy = SecurityPolicy.getInstance(this); + return mBinder; + } +} \ No newline at end of file diff --git a/src/com/android/emailcommon/service/IPolicyService.aidl b/src/com/android/emailcommon/service/IPolicyService.aidl new file mode 100644 index 000000000..646abf353 --- /dev/null +++ b/src/com/android/emailcommon/service/IPolicyService.aidl @@ -0,0 +1,30 @@ +/* + * 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.emailcommon.service; + +import com.android.emailcommon.service.PolicySet; + +interface IPolicyService { + boolean isActive(in PolicySet policies); + void policiesRequired(long accountId); + void updatePolicies(long accountId); + void setAccountHoldFlag(long accountId, boolean newState); + boolean isActiveAdmin(); + // This is about as oneway as you can get + oneway void remoteWipe(); + boolean isSupported(in PolicySet policies); + PolicySet clearUnsupportedPolicies(in PolicySet policies); +} \ No newline at end of file diff --git a/src/com/android/emailcommon/service/PolicyServiceProxy.java b/src/com/android/emailcommon/service/PolicyServiceProxy.java new file mode 100644 index 000000000..463d11dc6 --- /dev/null +++ b/src/com/android/emailcommon/service/PolicyServiceProxy.java @@ -0,0 +1,228 @@ +/* + * 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.emailcommon.service; + +import com.android.email.provider.EmailContent.Account; + +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +public class PolicyServiceProxy extends ServiceProxy implements IPolicyService { + private static final boolean DEBUG_PROXY = true; // STOPSHIP DO NOT CHECK THIS IN SET TO TRUE + private static final String TAG = "PolicyServiceProxy"; + + // The intent used by sync adapter services to connect to the PolicyService + public static final String POLICY_INTENT = "com.android.email.POLICY_INTENT"; + + private IPolicyService mService = null; + private Object mReturn = null; + + public PolicyServiceProxy(Context _context) { + super(_context, new Intent(POLICY_INTENT)); + } + + @Override + public void onConnected(IBinder binder) { + mService = IPolicyService.Stub.asInterface(binder); + } + + public IBinder asBinder() { + return null; + } + + @Override + public PolicySet clearUnsupportedPolicies(final PolicySet arg0) throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mReturn = mService.clearUnsupportedPolicies(arg0); + } + }, "clearUnsupportedPolicies"); + waitForCompletion(); + if (DEBUG_PROXY) { + Log.v(TAG, "clearUnsupportedPolicies: " + ((mReturn == null) ? "null" : mReturn)); + } + if (mReturn == null) { + // Can this happen? + return null; + } else { + return (PolicySet)mReturn; + } + } + + @Override + public boolean isActive(final PolicySet arg0) throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mReturn = mService.isActive(arg0); + } + }, "isActive"); + waitForCompletion(); + if (DEBUG_PROXY) { + Log.v(TAG, "isActive: " + ((mReturn == null) ? "null" : mReturn)); + } + if (mReturn == null) { + // Can this happen? + return false; + } else { + return (Boolean)mReturn; + } + } + + @Override + public boolean isActiveAdmin() throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mReturn = mService.isActiveAdmin(); + } + }, "isActiveAdmin"); + waitForCompletion(); + if (DEBUG_PROXY) { + Log.v(TAG, "isActiveAdmin: " + ((mReturn == null) ? "null" : mReturn)); + } + if (mReturn == null) { + // Can this happen? + return false; + } else { + return (Boolean)mReturn; + } + } + + @Override + public boolean isSupported(final PolicySet arg0) throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mReturn = mService.isSupported(arg0); + } + }, "isSupported"); + waitForCompletion(); + if (DEBUG_PROXY) { + Log.v(TAG, "isSupported: " + ((mReturn == null) ? "null" : mReturn)); + } + if (mReturn == null) { + // Can this happen? + return false; + } else { + return (Boolean)mReturn; + } + } + + @Override + public void policiesRequired(final long arg0) throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mService.policiesRequired(arg0); + } + }, "policiesRequired"); + } + + @Override + public void remoteWipe() throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mService.remoteWipe(); + } + }, "remoteWipe"); + } + + @Override + public void setAccountHoldFlag(final long arg0, final boolean arg1) throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mService.setAccountHoldFlag(arg0, arg1); + } + }, "setAccountHoldFlag"); + } + + @Override + public void updatePolicies(final long arg0) throws RemoteException { + setTask(new ProxyTask() { + public void run() throws RemoteException { + mService.updatePolicies(arg0); + } + }, "updatePolicies"); + } + + // Static methods that encapsulate the proxy calls above + public static boolean isActive(Context context, PolicySet policies) { + try { + return new PolicyServiceProxy(context).isActive(policies); + } catch (RemoteException e) { + } + return false; + } + + public static void policiesRequired(Context context, long accountId) { + try { + new PolicyServiceProxy(context).policiesRequired(accountId); + } catch (RemoteException e) { + throw new IllegalStateException("PolicyService transaction failed"); + } + } + + public static void updatePolicies(Context context, long accountId) { + try { + new PolicyServiceProxy(context).updatePolicies(accountId); + } catch (RemoteException e) { + throw new IllegalStateException("PolicyService transaction failed"); + } + } + + public static void setAccountHoldFlag(Context context, Account account, boolean newState) { + try { + new PolicyServiceProxy(context).setAccountHoldFlag(account.mId, newState); + } catch (RemoteException e) { + throw new IllegalStateException("PolicyService transaction failed"); + } + } + + public static boolean isActiveAdmin(Context context) { + try { + return new PolicyServiceProxy(context).isActiveAdmin(); + } catch (RemoteException e) { + } + return false; + } + + public static void remoteWipe(Context context) { + try { + new PolicyServiceProxy(context).remoteWipe(); + } catch (RemoteException e) { + throw new IllegalStateException("PolicyService transaction failed"); + } + } + + public static boolean isSupported(Context context, PolicySet policies) { + try { + return new PolicyServiceProxy(context).isSupported(policies); + } catch (RemoteException e) { + } + return false; + } + + public static PolicySet clearUnsupportedPolicies(Context context, PolicySet policies) { + try { + return new PolicyServiceProxy(context).clearUnsupportedPolicies(policies); + } catch (RemoteException e) { + } + throw new IllegalStateException("PolicyService transaction failed"); + } + +} + diff --git a/src/com/android/emailcommon/service/PolicySet.aidl b/src/com/android/emailcommon/service/PolicySet.aidl new file mode 100644 index 000000000..e825c62c3 --- /dev/null +++ b/src/com/android/emailcommon/service/PolicySet.aidl @@ -0,0 +1,19 @@ +/* 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.emailcommon.service; + +parcelable PolicySet; + diff --git a/src/com/android/emailcommon/service/PolicySet.java b/src/com/android/emailcommon/service/PolicySet.java new file mode 100644 index 000000000..3e93bedf2 --- /dev/null +++ b/src/com/android/emailcommon/service/PolicySet.java @@ -0,0 +1,356 @@ +/* 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.emailcommon.service; + +import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.AccountColumns; + +import android.app.admin.DevicePolicyManager; +import android.content.ContentValues; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + + +/** + * Class for tracking policies and reading/writing into accounts + */ +public class PolicySet implements Parcelable { + + // Security (provisioning) flags + // bits 0..4: password length (0=no password required) + private static final int PASSWORD_LENGTH_MASK = 31; + private static final int PASSWORD_LENGTH_SHIFT = 0; + public static final int PASSWORD_LENGTH_MAX = 30; + // bits 5..8: password mode + private static final int PASSWORD_MODE_SHIFT = 5; + private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT; + public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT; + public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT; + public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT; + // bits 9..13: password failures -> wipe device (0=disabled) + private static final int PASSWORD_MAX_FAILS_SHIFT = 9; + private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT; + public static final int PASSWORD_MAX_FAILS_MAX = 31; + // bits 14..24: seconds to screen lock (0=not required) + private static final int SCREEN_LOCK_TIME_SHIFT = 14; + private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT; + public static final int SCREEN_LOCK_TIME_MAX = 2047; + // bit 25: remote wipe capability required + private static final int REQUIRE_REMOTE_WIPE = 1 << 25; + // bit 26..35: password expiration (days; 0=not required) + private static final int PASSWORD_EXPIRATION_SHIFT = 26; + private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT; + public static final int PASSWORD_EXPIRATION_MAX = 1023; + // bit 36..43: password history (length; 0=not required) + private static final int PASSWORD_HISTORY_SHIFT = 36; + private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT; + public static final int PASSWORD_HISTORY_MAX = 255; + // bit 44..48: min complex characters (0=not required) + private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44; + private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT; + public static final int PASSWORD_COMPLEX_CHARS_MAX = 31; + // bit 49: requires device encryption + private static final long REQUIRE_ENCRYPTION = 1L << 49; + + /* Convert days to mSec (used for password expiration) */ + private static final long DAYS_TO_MSEC = 24 * 60 * 60 * 1000; + /* Small offset (2 minutes) added to policy expiration to make user testing easier. */ + private static final long EXPIRATION_OFFSET_MSEC = 2 * 60 * 1000; + + public final int mMinPasswordLength; + public final int mPasswordMode; + public final int mMaxPasswordFails; + public final int mMaxScreenLockTime; + public final boolean mRequireRemoteWipe; + public final int mPasswordExpirationDays; + public final int mPasswordHistory; + public final int mPasswordComplexChars; + public final boolean mRequireEncryption; + + public int getMinPasswordLengthForTest() { + return mMinPasswordLength; + } + + public int getPasswordModeForTest() { + return mPasswordMode; + } + + public int getMaxPasswordFailsForTest() { + return mMaxPasswordFails; + } + + public int getMaxScreenLockTimeForTest() { + return mMaxScreenLockTime; + } + + public boolean isRequireRemoteWipeForTest() { + return mRequireRemoteWipe; + } + + public boolean isRequireEncryptionForTest() { + return mRequireEncryption; + } + + /** + * Create from raw values. + * @param minPasswordLength (0=not enforced) + * @param passwordMode + * @param maxPasswordFails (0=not enforced) + * @param maxScreenLockTime in seconds (0=not enforced) + * @param requireRemoteWipe + * @param passwordExpirationDays in days (0=not enforced) + * @param passwordHistory (0=not enforced) + * @param passwordComplexChars (0=not enforced) + * @throws IllegalArgumentException for illegal arguments. + */ + public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails, + int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpirationDays, + int passwordHistory, int passwordComplexChars, boolean requireEncryption) + throws IllegalArgumentException { + // If we're not enforcing passwords, make sure we clean up related values, since EAS + // can send non-zero values for any or all of these + if (passwordMode == PASSWORD_MODE_NONE) { + maxPasswordFails = 0; + maxScreenLockTime = 0; + minPasswordLength = 0; + passwordComplexChars = 0; + passwordHistory = 0; + passwordExpirationDays = 0; + } else { + if ((passwordMode != PASSWORD_MODE_SIMPLE) && + (passwordMode != PASSWORD_MODE_STRONG)) { + throw new IllegalArgumentException("password mode"); + } + // If we're only requiring a simple password, set complex chars to zero; note + // that EAS can erroneously send non-zero values in this case + if (passwordMode == PASSWORD_MODE_SIMPLE) { + passwordComplexChars = 0; + } + // The next four values have hard limits which cannot be supported if exceeded. + if (minPasswordLength > PASSWORD_LENGTH_MAX) { + throw new IllegalArgumentException("password length"); + } + if (passwordExpirationDays > PASSWORD_EXPIRATION_MAX) { + throw new IllegalArgumentException("password expiration"); + } + if (passwordHistory > PASSWORD_HISTORY_MAX) { + throw new IllegalArgumentException("password history"); + } + if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) { + throw new IllegalArgumentException("complex chars"); + } + // This value can be reduced (which actually increases security) if necessary + if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) { + maxPasswordFails = PASSWORD_MAX_FAILS_MAX; + } + // This value can be reduced (which actually increases security) if necessary + if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) { + maxScreenLockTime = SCREEN_LOCK_TIME_MAX; + } + } + mMinPasswordLength = minPasswordLength; + mPasswordMode = passwordMode; + mMaxPasswordFails = maxPasswordFails; + mMaxScreenLockTime = maxScreenLockTime; + mRequireRemoteWipe = requireRemoteWipe; + mPasswordExpirationDays = passwordExpirationDays; + mPasswordHistory = passwordHistory; + mPasswordComplexChars = passwordComplexChars; + mRequireEncryption = requireEncryption; + } + + /** + * Create from values encoded in an account + * @param account + */ + public PolicySet(Account account) { + this(account.mSecurityFlags); + } + + /** + * Create from values encoded in an account flags int + */ + public PolicySet(long flags) { + mMinPasswordLength = + (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT); + mPasswordMode = + (int) (flags & PASSWORD_MODE_MASK); + mMaxPasswordFails = + (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT); + mMaxScreenLockTime = + (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT); + mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE); + mPasswordExpirationDays = + (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT); + mPasswordHistory = + (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT); + mPasswordComplexChars = + (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT); + mRequireEncryption = 0 != (flags & REQUIRE_ENCRYPTION); + } + + /** + * Helper to map our internal encoding to DevicePolicyManager password modes. + */ + public int getDPManagerPasswordQuality() { + switch (mPasswordMode) { + case PASSWORD_MODE_SIMPLE: + return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + case PASSWORD_MODE_STRONG: + return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; + default: + return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED; + } + } + + /** + * Helper to map expiration times to the millisecond values used by DevicePolicyManager. + */ + public long getDPManagerPasswordExpirationTimeout() { + long result = mPasswordExpirationDays * DAYS_TO_MSEC; + // Add a small offset to the password expiration. This makes it easier to test + // by changing (for example) 1 day to 1 day + 5 minutes. If you set an expiration + // that is within the warning period, you should get a warning fairly quickly. + if (result > 0) { + result += EXPIRATION_OFFSET_MSEC; + } + return result; + } + + /** + * Record flags (and a sync key for the flags) into an Account + * Note: the hash code is defined as the encoding used in Account + * + * @param account to write the values mSecurityFlags and mSecuritySyncKey + * @param syncKey the value to write into the account's mSecuritySyncKey + * @param update if true, also writes the account back to the provider (updating only + * the fields changed by this API) + * @param context a context for writing to the provider + * @return true if the actual policies changed, false if no change (note, sync key + * does not affect this) + */ + public boolean writeAccount(Account account, String syncKey, boolean update, + Context context) { + long newFlags = getSecurityCode(); + boolean dirty = (newFlags != account.mSecurityFlags); + account.mSecurityFlags = newFlags; + account.mSecuritySyncKey = syncKey; + if (update) { + if (account.isSaved()) { + ContentValues cv = new ContentValues(); + cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags); + cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey); + account.update(context, cv); + } else { + account.save(context); + } + } + return dirty; + } + + @Override + public boolean equals(Object o) { + if (o instanceof PolicySet) { + PolicySet other = (PolicySet)o; + return (this.getSecurityCode() == other.getSecurityCode()); + } + return false; + } + + /** + * Supports Parcelable + */ + public int describeContents() { + return 0; + } + + /** + * Supports Parcelable + */ + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public PolicySet createFromParcel(Parcel in) { + return new PolicySet(in); + } + + public PolicySet[] newArray(int size) { + return new PolicySet[size]; + } + }; + + /** + * Supports Parcelable + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMinPasswordLength); + dest.writeInt(mPasswordMode); + dest.writeInt(mMaxPasswordFails); + dest.writeInt(mMaxScreenLockTime); + dest.writeInt(mRequireRemoteWipe ? 1 : 0); + dest.writeInt(mPasswordExpirationDays); + dest.writeInt(mPasswordHistory); + dest.writeInt(mPasswordComplexChars); + dest.writeInt(mRequireEncryption ? 1 : 0); + } + + /** + * Supports Parcelable + */ + public PolicySet(Parcel in) { + mMinPasswordLength = in.readInt(); + mPasswordMode = in.readInt(); + mMaxPasswordFails = in.readInt(); + mMaxScreenLockTime = in.readInt(); + mRequireRemoteWipe = in.readInt() == 1; + mPasswordExpirationDays = in.readInt(); + mPasswordHistory = in.readInt(); + mPasswordComplexChars = in.readInt(); + mRequireEncryption = in.readInt() == 1; + } + + @Override + public int hashCode() { + long code = getSecurityCode(); + return (int) code; + } + + public long getSecurityCode() { + long flags = 0; + flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT; + flags |= mPasswordMode; + flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT; + flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT; + if (mRequireRemoteWipe) flags |= REQUIRE_REMOTE_WIPE; + flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT; + flags |= (long)mPasswordExpirationDays << PASSWORD_EXPIRATION_SHIFT; + flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT; + if (mRequireEncryption) flags |= REQUIRE_ENCRYPTION; + return flags; + } + + @Override + public String toString() { + return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode + + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max=" + + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + + " pw-expiration=" + mPasswordExpirationDays + + " pw-history=" + mPasswordHistory + + " pw-complex-chars=" + mPasswordComplexChars + + " require-encryption=" + mRequireEncryption + "}"; + } +} + diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index 32441b094..e1254a5e3 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -17,8 +17,6 @@ package com.android.exchange; -import com.android.email.SecurityPolicy; -import com.android.email.SecurityPolicy.PolicySet; import com.android.email.Utility; import com.android.email.mail.Address; import com.android.email.mail.MeetingInfo; @@ -37,6 +35,8 @@ import com.android.email.provider.EmailContent.SyncColumns; import com.android.emailcommon.service.EmailServiceConstants; import com.android.emailcommon.service.EmailServiceProxy; import com.android.emailcommon.service.EmailServiceStatus; +import com.android.emailcommon.service.PolicyServiceProxy; +import com.android.emailcommon.service.PolicySet; import com.android.exchange.adapter.AbstractSyncAdapter; import com.android.exchange.adapter.AccountSyncAdapter; import com.android.exchange.adapter.CalendarSyncAdapter; @@ -46,11 +46,11 @@ import com.android.exchange.adapter.FolderSyncParser; import com.android.exchange.adapter.GalParser; import com.android.exchange.adapter.MeetingResponseParser; import com.android.exchange.adapter.MoveItemsParser; -import com.android.exchange.adapter.Parser.EasParserException; import com.android.exchange.adapter.PingParser; import com.android.exchange.adapter.ProvisionParser; import com.android.exchange.adapter.Serializer; import com.android.exchange.adapter.Tags; +import com.android.exchange.adapter.Parser.EasParserException; import com.android.exchange.provider.GalResult; import com.android.exchange.utility.CalendarUtilities; @@ -1435,24 +1435,23 @@ public class EasSyncService extends AbstractSyncService { // by the server ProvisionParser pp = canProvision(); if (pp != null) { - SecurityPolicy sp = SecurityPolicy.getInstance(mContext); // Get the policies from ProvisionParser PolicySet ps = pp.getPolicySet(); // Update the account with a null policyKey (the key we've gotten is // temporary and cannot be used for syncing) ps.writeAccount(mAccount, null, true, mContext); // Make sure that SecurityPolicy is up-to-date - sp.updatePolicies(mAccount.mId); + PolicyServiceProxy.updatePolicies(mContext, mAccount.mId); if (pp.getRemoteWipe()) { // We've gotten a remote wipe command ExchangeService.alwaysLog("!!! Remote wipe request received"); // Start by setting the account to security hold - sp.setAccountHoldFlag(mContext, mAccount, true); + PolicyServiceProxy.setAccountHoldFlag(mContext, mAccount, true); // Force a stop to any running syncs for this account (except this one) ExchangeService.stopNonAccountMailboxSyncsForAccount(mAccount.mId); // If we're not the admin, we can't do the wipe, so just return - if (!sp.isActiveAdmin()) { + if (!PolicyServiceProxy.isActiveAdmin(mContext)) { ExchangeService.alwaysLog("!!! Not device admin; can't wipe"); return false; } @@ -1468,9 +1467,9 @@ public class EasSyncService extends AbstractSyncService { } // Then, tell SecurityPolicy to wipe the device ExchangeService.alwaysLog("!!! Executing remote wipe"); - sp.remoteWipe(); + PolicyServiceProxy.remoteWipe(mContext); return false; - } else if (sp.isActive(ps)) { + } else if (PolicyServiceProxy.isActive(mContext, ps)) { // See if the required policies are in force; if they are, acknowledge the policies // to the server and get the final policy key String policyKey = acknowledgeProvision(pp.getPolicyKey(), PROVISION_STATUS_OK); @@ -1483,7 +1482,7 @@ public class EasSyncService extends AbstractSyncService { } } else { // Notify that we are blocked because of policies - sp.policiesRequired(mAccount.mId); + PolicyServiceProxy.policiesRequired(mContext, mAccount.mId); } } return false; @@ -1762,15 +1761,14 @@ public class EasSyncService extends AbstractSyncService { String key = mAccount.mSecuritySyncKey; if (!TextUtils.isEmpty(key)) { PolicySet ps = new PolicySet(mAccount); - SecurityPolicy sp = SecurityPolicy.getInstance(mContext); - if (!sp.isActive(ps)) { + if (!PolicyServiceProxy.isActive(mContext, ps)) { cv.clear(); cv.put(AccountColumns.SECURITY_FLAGS, 0); cv.putNull(AccountColumns.SECURITY_SYNC_KEY); long accountId = mAccount.mId; mContentResolver.update(ContentUris.withAppendedId( Account.CONTENT_URI, accountId), cv, null, null); - sp.policiesRequired(accountId); + PolicyServiceProxy.policiesRequired(mContext, accountId); } } diff --git a/src/com/android/exchange/adapter/ProvisionParser.java b/src/com/android/exchange/adapter/ProvisionParser.java index c5ef7ae14..21eae349c 100644 --- a/src/com/android/exchange/adapter/ProvisionParser.java +++ b/src/com/android/exchange/adapter/ProvisionParser.java @@ -15,8 +15,8 @@ package com.android.exchange.adapter; -import com.android.email.SecurityPolicy; -import com.android.email.SecurityPolicy.PolicySet; +import com.android.emailcommon.service.PolicyServiceProxy; +import com.android.emailcommon.service.PolicySet; import com.android.exchange.EasSyncService; import org.xmlpull.v1.XmlPullParser; @@ -61,8 +61,7 @@ public class ProvisionParser extends Parser { } public void clearUnsupportedPolicies() { - mPolicySet = SecurityPolicy.getInstance(mService.mContext) - .clearUnsupportedPolicies(mPolicySet); + mPolicySet = PolicyServiceProxy.clearUnsupportedPolicies(mService.mContext, mPolicySet); mIsSupportable = true; } @@ -209,11 +208,12 @@ public class ProvisionParser extends Parser { } } - mPolicySet = new SecurityPolicy.PolicySet(minPasswordLength, passwordMode, + mPolicySet = new PolicySet(minPasswordLength, passwordMode, maxPasswordFails, maxScreenLockTime, true, passwordExpirationDays, passwordHistory, passwordComplexChars, encryptionRequired); + // We can only determine whether encryption is supported on device by using isSupported here - if (!SecurityPolicy.getInstance(mService.mContext).isSupported(mPolicySet)) { + if (!PolicyServiceProxy.isSupported(mService.mContext, mPolicySet)) { log("SecurityPolicy reports PolicySet not supported."); mIsSupportable = false; } diff --git a/tests/src/com/android/email/SecurityPolicyTests.java b/tests/src/com/android/email/SecurityPolicyTests.java index 5719f884d..33edeb91d 100644 --- a/tests/src/com/android/email/SecurityPolicyTests.java +++ b/tests/src/com/android/email/SecurityPolicyTests.java @@ -16,15 +16,15 @@ package com.android.email; -import com.android.email.SecurityPolicy.PolicySet; import com.android.email.provider.ContentCache; import com.android.email.provider.EmailContent; +import com.android.email.provider.EmailProvider; +import com.android.email.provider.ProviderTestUtils; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.AccountColumns; import com.android.email.provider.EmailContent.Mailbox; import com.android.email.provider.EmailContent.Message; -import com.android.email.provider.EmailProvider; -import com.android.email.provider.ProviderTestUtils; +import com.android.emailcommon.service.PolicySet; import android.app.admin.DevicePolicyManager; import android.content.ContentUris; diff --git a/tests/src/com/android/exchange/adapter/ProvisionParserTests.java b/tests/src/com/android/exchange/adapter/ProvisionParserTests.java index e69d12506..7daf0cc9f 100644 --- a/tests/src/com/android/exchange/adapter/ProvisionParserTests.java +++ b/tests/src/com/android/exchange/adapter/ProvisionParserTests.java @@ -16,7 +16,7 @@ package com.android.exchange.adapter; -import com.android.email.SecurityPolicy.PolicySet; +import com.android.emailcommon.service.PolicySet; import java.io.ByteArrayInputStream; import java.io.IOException;