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
This commit is contained in:
Marc Blank 2011-02-08 18:54:56 -08:00
parent 0d4fc55861
commit 9ba506c4dd
15 changed files with 757 additions and 360 deletions

View File

@ -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.

View File

@ -358,6 +358,18 @@
android:resource="@xml/syncadapter_pop_imap" />
</service>
<!-- Require provider permission to use our Policy and Account services -->
<service
android:name=".service.PolicyService"
android:enabled="true"
android:permission="com.android.email.permission.ACCESS_PROVIDER"
>
<intent-filter>
<action
android:name="com.android.email.POLICY_INTENT" />
</intent-filter>
</service>
<!--EXCHANGE-REMOVE-SECTION-START-->
<!--Required stanza to register the EAS EmailSyncAdapterService with SyncManager -->
<service

View File

@ -21,6 +21,7 @@ import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
import com.android.email.service.EmailBroadcastProcessorService;
import com.android.emailcommon.service.PolicySet;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
@ -31,8 +32,6 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
@ -418,12 +417,22 @@ public class SecurityPolicy {
}
}
/**
* Convenience method; see javadoc below
*/
public static void setAccountHoldFlag(Context context, long accountId, boolean newState) {
Account account = Account.restoreAccountWithId(context, accountId);
if (account != null) {
setAccountHoldFlag(context, account, newState);
}
}
/**
* API: Set/Clear the "hold" flag in any account. This flag serves a dual purpose:
* Setting it gives us an indication that it was blocked, and clearing it gives EAS a
* signal to try syncing again.
* @param context
* @param account The account to update
* @param account the account whose hold flag is to be set/cleared
* @param newState true = security hold, false = free to sync
*/
public static void setAccountHoldFlag(Context context, Account account, boolean newState) {
@ -485,336 +494,6 @@ public class SecurityPolicy {
Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin.");
}
}
/**
* Class for tracking policies and reading/writing into accounts
*/
public static 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;
/*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<PolicySet> CREATOR
= new Parcelable.Creator<PolicySet>() {
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.
*

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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<PolicySet> CREATOR
= new Parcelable.Creator<PolicySet>() {
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 + "}";
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;