Logic to move phone into security-admin mode
* Create notification to display when syncs fail due to security * Create psuedo-activity (no UI) to manage device admin state transitions * Clean up and flesh out SecurityPolicy APIs' * Add placeholders in EasSyncService showing how to react when policies are not met and sync cannot continue. Note: There are some STOPSHIP todo's at the top of SecurityPolicy.java. These should explain any code that you might think is "missing".
This commit is contained in:
parent
4d2a701844
commit
3d2b3b3b35
|
@ -115,6 +115,11 @@
|
|||
android:label="@string/account_settings_action"
|
||||
>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.setup.AccountSecurity"
|
||||
android.label="@string/account_security_title"
|
||||
>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.Debug"
|
||||
|
|
|
@ -502,6 +502,19 @@
|
|||
<string name="account_setup_failed_security_policies_unsupported">
|
||||
This server requires security features your phone does not support.</string>
|
||||
|
||||
<!-- Notification ticker when device security required -->
|
||||
<string name="security_notification_ticker_fmt">
|
||||
Account \"<xliff:g id="account">%s</xliff:g>\" requires security settings update.
|
||||
</string>
|
||||
<!-- Notification content title when device security required -->
|
||||
<string name="security_notification_content_title">Update Security Settings</string>
|
||||
<!-- Title of the activity that dispatches changes to device security. Not normally seen. -->
|
||||
<string name="account_security_title">Device Security</string>
|
||||
<!-- Additional diagnostic text when the email app asserts control of the phone. -->
|
||||
<string name="account_security_policy_explanation_fmt">
|
||||
The server <xliff:g id="server">%s</xliff:g> requires that you allow it to remotely control
|
||||
some security features of your phone.</string>
|
||||
|
||||
<!-- "Setup could not finish" dialog action button -->
|
||||
<string name="account_setup_failed_dlg_edit_details_action">Edit details</string>
|
||||
<!-- On Settings screen, section heading -->
|
||||
|
@ -585,9 +598,9 @@
|
|||
<string name="system_account_create_failed">The AccountManager could not create the Account; please try again.</string>
|
||||
|
||||
<!-- Strings that support the DeviceAdmin / DevicePolicyManager API -->
|
||||
<!-- Name of the DeviceAdmin (seen in settings - anywhere else?) -->
|
||||
<string name="device_admin_label">Email Device Administrator</string>
|
||||
<!-- Long-form description of the DeviceAdmin (seen in settings - anywhere else?) -->
|
||||
<string name="device_admin_description">Email Device Administrator - Long Description</string>
|
||||
|
||||
<!-- Name of the DeviceAdmin (seen in settings & in user confirmation screen) -->
|
||||
<string name="device_admin_label">Email</string>
|
||||
<!-- Long-form description of the DeviceAdmin (2nd line in settings & in user conf. screen) -->
|
||||
<string name="device_admin_description">Enables server-specified security policies</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -16,29 +16,48 @@
|
|||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.activity.setup.AccountSecurity;
|
||||
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.MailService;
|
||||
|
||||
import android.app.DeviceAdmin;
|
||||
import android.app.DevicePolicyManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Utility functions to support reading and writing security policies
|
||||
*
|
||||
* STOPSHIP - these TODO items are all part of finishing the feature
|
||||
* TODO: When user sets password, and conditions are now satisfied, restart syncs
|
||||
* TODO: When accounts are deleted, reduce policy and/or give up admin status
|
||||
* TODO: Provide a way to check for policy issues at synchronous times such as entering
|
||||
* message list or folder list.
|
||||
* TODO: Implement local wipe after failed passwords
|
||||
*
|
||||
*/
|
||||
public class SecurityPolicy {
|
||||
|
||||
/** STOPSHIP - ok to check in true for now, but must be false for shipping */
|
||||
/** DO NOT CHECK IN WHILE 'true' */
|
||||
private static final boolean DEBUG_ALWAYS_ACTIVE = true;
|
||||
private static final boolean DEBUG_ALWAYS_ACTIVE = false;
|
||||
|
||||
private static SecurityPolicy sInstance = null;
|
||||
private Context mContext;
|
||||
private DevicePolicyManager mDPM;
|
||||
private ComponentName mAdminName;
|
||||
private PolicySet mAggregatePolicy;
|
||||
private boolean mNotificationActive;
|
||||
private boolean mAdminEnabled;
|
||||
|
||||
private static final PolicySet NO_POLICY_SET =
|
||||
new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
|
||||
|
@ -81,6 +100,7 @@ public class SecurityPolicy {
|
|||
mDPM = null;
|
||||
mAdminName = new ComponentName(context, PolicyAdmin.class);
|
||||
mAggregatePolicy = null;
|
||||
mNotificationActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,12 +139,8 @@ public class SecurityPolicy {
|
|||
int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS);
|
||||
if (flags != 0) {
|
||||
PolicySet p = new PolicySet(flags);
|
||||
if (p.mMinPasswordLength > 0) {
|
||||
minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
|
||||
}
|
||||
if (p.mPasswordMode > 0) {
|
||||
passwordMode = Math.max(p.mPasswordMode, passwordMode);
|
||||
}
|
||||
minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
|
||||
passwordMode = Math.max(p.mPasswordMode, passwordMode);
|
||||
if (p.mMaxPasswordFails > 0) {
|
||||
maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
|
||||
}
|
||||
|
@ -139,6 +155,12 @@ public class SecurityPolicy {
|
|||
c.close();
|
||||
}
|
||||
if (policiesFound) {
|
||||
// final cleanup pass converts any untouched min/max values to zero (not specified)
|
||||
if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
|
||||
if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
|
||||
if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
|
||||
if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
|
||||
|
||||
return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
|
||||
maxScreenLockTime, requireRemoteWipe);
|
||||
} else {
|
||||
|
@ -185,51 +207,58 @@ public class SecurityPolicy {
|
|||
|
||||
/**
|
||||
* API: Report that policies may have been updated due to rewriting values in an Account.
|
||||
* @param accountId the account that has been updated
|
||||
*/
|
||||
public synchronized void updatePolicies() {
|
||||
public synchronized void updatePolicies(long accountId) {
|
||||
mAggregatePolicy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* API: Query used to determine if a given policy is "active" (the device is operating at
|
||||
* the required security level). This is used when creating new accounts. This method
|
||||
* is for queries only, and does not trigger any change in device state.
|
||||
* the required security level).
|
||||
*
|
||||
* @param policies the policies requested
|
||||
* This can be used when syncing a specific account, by passing a specific set of policies
|
||||
* for that account. Or, it can be used at any time to compare the device
|
||||
* state against the aggregate set of device policies stored in all accounts.
|
||||
*
|
||||
* This method is for queries only, and does not trigger any change in device state.
|
||||
*
|
||||
* @param policies the policies requested, or null to check aggregate stored policies
|
||||
* @return true if the policies are active, false if not active
|
||||
*/
|
||||
public boolean isActive(PolicySet policies) {
|
||||
DevicePolicyManager dpm = getDPM();
|
||||
if (dpm.isAdminActive(mAdminName)) {
|
||||
// check each policy
|
||||
PolicySet aggregate;
|
||||
synchronized (this) {
|
||||
if (mAggregatePolicy == null) {
|
||||
mAggregatePolicy = computeAggregatePolicy();
|
||||
// select aggregate set if needed
|
||||
if (policies == null) {
|
||||
synchronized (this) {
|
||||
if (mAggregatePolicy == null) {
|
||||
mAggregatePolicy = computeAggregatePolicy();
|
||||
}
|
||||
policies = mAggregatePolicy;
|
||||
}
|
||||
aggregate = mAggregatePolicy;
|
||||
}
|
||||
// quick check for the "empty set" of no policies
|
||||
if (aggregate == NO_POLICY_SET) {
|
||||
if (policies == NO_POLICY_SET) {
|
||||
return true;
|
||||
}
|
||||
// check each policy explicitly
|
||||
if (aggregate.mMinPasswordLength > 0) {
|
||||
if (dpm.getPasswordMinimumLength(mAdminName) < aggregate.mMinPasswordLength) {
|
||||
if (policies.mMinPasswordLength > 0) {
|
||||
if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (aggregate.mPasswordMode > 0) {
|
||||
if (dpm.getPasswordQuality(mAdminName) < aggregate.getDPManagerPasswordMode()) {
|
||||
if (policies.mPasswordMode > 0) {
|
||||
if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
|
||||
return false;
|
||||
}
|
||||
if (!dpm.isActivePasswordSufficient()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (aggregate.mMaxScreenLockTime > 0) {
|
||||
if (policies.mMaxScreenLockTime > 0) {
|
||||
// Note, we use seconds, dpm uses milliseconds
|
||||
if (dpm.getMaximumTimeToLock(mAdminName) > aggregate.mMaxScreenLockTime * 1000) {
|
||||
if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -241,14 +270,101 @@ public class SecurityPolicy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sync service should call this any time a sync fails due to isActive() returning false.
|
||||
* Set the requested security level based on the aggregate set of requests
|
||||
*/
|
||||
public void setActivePolicies() {
|
||||
DevicePolicyManager dpm = getDPM();
|
||||
if (dpm.isAdminActive(mAdminName)) {
|
||||
// compute aggregate set if needed
|
||||
PolicySet policies;
|
||||
synchronized (this) {
|
||||
if (mAggregatePolicy == null) {
|
||||
mAggregatePolicy = computeAggregatePolicy();
|
||||
}
|
||||
policies = mAggregatePolicy;
|
||||
}
|
||||
// set each policy in the policy manager
|
||||
// password mode & length
|
||||
dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
|
||||
dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
|
||||
// screen lock time
|
||||
dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
|
||||
// local wipe (failed passwords limit)
|
||||
dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API: Sync service should call this any time a sync fails due to isActive() returning false.
|
||||
* This will kick off the notify-acquire-admin-state process and/or increase the security level.
|
||||
* The caller needs to write the required policies into this account before making this call.
|
||||
* Should not be called from UI thread - uses DB lookups to prepare new notifications
|
||||
*
|
||||
* @param accountId the account for which sync cannot proceed
|
||||
*/
|
||||
public void policiesRequired(long accountId) {
|
||||
// implement....
|
||||
synchronized (this) {
|
||||
if (mNotificationActive) {
|
||||
// no need to do anything - we've already been notified, and we've already
|
||||
// put up a notification
|
||||
return;
|
||||
} else {
|
||||
// Prepare & post a notification
|
||||
// record that we're watching this one
|
||||
mNotificationActive = true;
|
||||
}
|
||||
}
|
||||
// At this point, we will put up a notification
|
||||
Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
|
||||
|
||||
String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
|
||||
account.getDisplayName());
|
||||
String contentTitle = mContext.getString(R.string.security_notification_content_title);
|
||||
String contentText = account.getDisplayName();
|
||||
String ringtoneString = account.getRingtone();
|
||||
Uri ringTone = (ringtoneString == null) ? null : Uri.parse(ringtoneString);
|
||||
boolean vibrate = 0 != (account.mFlags & Account.FLAGS_VIBRATE);
|
||||
|
||||
Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId);
|
||||
PendingIntent pending =
|
||||
PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
Notification notification = new Notification(R.drawable.stat_notify_email_generic,
|
||||
tickerText, System.currentTimeMillis());
|
||||
notification.setLatestEventInfo(mContext, contentTitle, contentText, pending);
|
||||
|
||||
// Use the account's notification rules for sound & vibrate (but always notify)
|
||||
notification.sound = ringTone;
|
||||
if (vibrate) {
|
||||
notification.defaults |= Notification.DEFAULT_VIBRATE;
|
||||
}
|
||||
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
|
||||
notification.defaults |= Notification.DEFAULT_LIGHTS;
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(MailService.NOTIFICATION_ID_SECURITY_NEEDED, notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the notification's intent receiver to register that the notification can be
|
||||
* cleared now.
|
||||
*/
|
||||
public synchronized void clearNotification(long accountId) {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(MailService.NOTIFICATION_ID_SECURITY_NEEDED);
|
||||
mNotificationActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* API: Remote wipe (from server). This is final, there is no confirmation.
|
||||
*/
|
||||
public void remoteWipe(long accountId) {
|
||||
DevicePolicyManager dpm = getDPM();
|
||||
if (dpm.isAdminActive(mAdminName)) {
|
||||
dpm.wipeData(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -291,9 +407,10 @@ public class SecurityPolicy {
|
|||
* @param maxPasswordFails (0=not enforced)
|
||||
* @param maxScreenLockTime in seconds (0=not enforced)
|
||||
* @param requireRemoteWipe
|
||||
* @throws IllegalArgumentException when any arguments are outside of legal ranges.
|
||||
*/
|
||||
public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
|
||||
int maxScreenLockTime, boolean requireRemoteWipe) {
|
||||
int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException {
|
||||
if (minPasswordLength > PASSWORD_LENGTH_MAX) {
|
||||
throw new IllegalArgumentException("password length");
|
||||
}
|
||||
|
@ -339,9 +456,9 @@ public class SecurityPolicy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper to map DevicePolicyManager password modes to our internal encoding.
|
||||
* Helper to map our internal encoding to DevicePolicyManager password modes.
|
||||
*/
|
||||
public int getDPManagerPasswordMode() {
|
||||
public int getDPManagerPasswordQuality() {
|
||||
switch (mPasswordMode) {
|
||||
case PASSWORD_MODE_SIMPLE:
|
||||
return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
|
||||
|
@ -355,11 +472,32 @@ public class SecurityPolicy {
|
|||
/**
|
||||
* 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 void writeAccount(Account account, String syncKey) {
|
||||
account.mSecurityFlags = hashCode();
|
||||
public boolean writeAccount(Account account, String syncKey, boolean update,
|
||||
Context context) {
|
||||
int newFlags = hashCode();
|
||||
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
|
||||
|
@ -400,19 +538,47 @@ public class SecurityPolicy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Device Policy administrator. This is primarily a listener for device state changes.
|
||||
* If we are not the active device admin, try to become so.
|
||||
*
|
||||
* @return true if we are already active, false if we are not
|
||||
*/
|
||||
private static class PolicyAdmin extends DeviceAdmin {
|
||||
public boolean isActiveAdmin() {
|
||||
DevicePolicyManager dpm = getDPM();
|
||||
return dpm.isAdminActive(mAdminName);
|
||||
}
|
||||
|
||||
boolean mEnabled = false;
|
||||
/**
|
||||
* Report admin component name - for making calls into device policy manager
|
||||
*/
|
||||
public ComponentName getAdminComponent() {
|
||||
return mAdminName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal handler for enabled/disabled transitions. Handles DeviceAdmin.onEnabled and
|
||||
* and DeviceAdmin.onDisabled.
|
||||
*/
|
||||
private void onAdminEnabled(boolean isEnabled) {
|
||||
if (isEnabled && !mAdminEnabled) {
|
||||
// TODO: transition to enabled state
|
||||
} else if (!isEnabled && mAdminEnabled) {
|
||||
// TODO: transition to disabled state
|
||||
}
|
||||
mAdminEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Device Policy administrator. This is primarily a listener for device state changes.
|
||||
* Note: This is instantiated by incoming messages.
|
||||
*/
|
||||
public static class PolicyAdmin extends DeviceAdmin {
|
||||
|
||||
/**
|
||||
* Called after the administrator is first enabled.
|
||||
*/
|
||||
@Override
|
||||
public void onEnabled(Context context, Intent intent) {
|
||||
mEnabled = true;
|
||||
// do something
|
||||
SecurityPolicy.getInstance(context).onAdminEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -420,8 +586,7 @@ public class SecurityPolicy {
|
|||
*/
|
||||
@Override
|
||||
public void onDisabled(Context context, Intent intent) {
|
||||
mEnabled = false;
|
||||
// do something
|
||||
SecurityPolicy.getInstance(context).onAdminEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -325,7 +325,7 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
|
|||
// clear notifications here
|
||||
NotificationManager notificationManager = (NotificationManager)
|
||||
getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(MailService.NEW_MESSAGE_NOTIFICATION_ID);
|
||||
notificationManager.cancel(MailService.NOTIFICATION_ID_NEW_MESSAGES);
|
||||
restoreListPosition();
|
||||
autoRefreshStaleMailbox();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (C) 2010 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.activity.setup;
|
||||
|
||||
import com.android.email.R;
|
||||
import com.android.email.SecurityPolicy;
|
||||
import com.android.email.provider.EmailContent.Account;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Psuedo-activity (no UI) to bootstrap the user up to a higher desired security level. This
|
||||
* bootstrap requires the following steps.
|
||||
*
|
||||
* 1. Confirm the account of interest has any security policies defined - exit early if not
|
||||
* 2. If not actively administrating the device, ask Device Policy Manager to start that
|
||||
* 3. When we are actively administrating, check current policies and see if they're sufficient
|
||||
* 4. If not, set policies
|
||||
* 5. If necessary, request for user to update device password
|
||||
*/
|
||||
public class AccountSecurity extends Activity {
|
||||
|
||||
private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity.setup.ACCOUNT_ID";
|
||||
|
||||
private static final int REQUEST_ENABLE = 1;
|
||||
|
||||
/**
|
||||
* Used for generating intent for this activity (which is intended to be launched
|
||||
* from a notification.)
|
||||
*
|
||||
* @param context Calling context for building the intent
|
||||
* @param accountId The account of interest
|
||||
* @return an Intent which can be used to view that account
|
||||
*/
|
||||
public static Intent actionUpdateSecurityIntent(Context context, long accountId) {
|
||||
Intent intent = new Intent(context, AccountSecurity.class);
|
||||
intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent i = getIntent();
|
||||
long accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1);
|
||||
SecurityPolicy security = SecurityPolicy.getInstance(this);
|
||||
security.clearNotification(accountId);
|
||||
if (accountId != -1) {
|
||||
// TODO: spin up a thread to do this in the background, because of DB ops
|
||||
Account account = Account.restoreAccountWithId(this, accountId);
|
||||
if (account != null) {
|
||||
if (account.mSecurityFlags != 0) {
|
||||
// This account wants to control security
|
||||
if (!security.isActiveAdmin()) {
|
||||
// try to become active - must happen here in this activity, to get result
|
||||
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
|
||||
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
|
||||
security.getAdminComponent());
|
||||
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
|
||||
this.getString(R.string.account_security_policy_explanation_fmt,
|
||||
account.getDisplayName()));
|
||||
startActivityForResult(intent, REQUEST_ENABLE);
|
||||
// keep this activity on stack to process result
|
||||
return;
|
||||
} else {
|
||||
// already active - try to set actual policies, finish, and return
|
||||
setActivePolicies();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the eventual result of the user allowing us to become an active device admin
|
||||
*/
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_ENABLE:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// now active - try to set actual policies
|
||||
setActivePolicies();
|
||||
} else {
|
||||
// failed - just give up and go away
|
||||
}
|
||||
}
|
||||
finish();
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Now that we are connected as an active device admin, try to set the device to the
|
||||
* correct security level, and ask for a password if necessary.
|
||||
*/
|
||||
private void setActivePolicies() {
|
||||
SecurityPolicy sp = SecurityPolicy.getInstance(this);
|
||||
// check current security level - if sufficient, we're done!
|
||||
if (sp.isActive(null)) {
|
||||
return;
|
||||
}
|
||||
// set current security level
|
||||
sp.setActivePolicies();
|
||||
// check current security level - if sufficient, we're done!
|
||||
if (sp.isActive(null)) {
|
||||
return;
|
||||
}
|
||||
// if not sufficient, launch the activity to have the user set a new password.
|
||||
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
|
@ -53,7 +53,8 @@ public class MailService extends Service {
|
|||
|
||||
private static final String LOG_TAG = "Email-MailService";
|
||||
|
||||
public static int NEW_MESSAGE_NOTIFICATION_ID = 1;
|
||||
public static int NOTIFICATION_ID_NEW_MESSAGES = 1;
|
||||
public static int NOTIFICATION_ID_SECURITY_NEEDED = 2;
|
||||
|
||||
private static final String ACTION_CHECK_MAIL =
|
||||
"com.android.email.intent.action.MAIL_SERVICE_WAKEUP";
|
||||
|
@ -201,7 +202,7 @@ public class MailService extends Service {
|
|||
// but that's an edge condition and this is much safer.
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(NEW_MESSAGE_NOTIFICATION_ID);
|
||||
notificationManager.cancel(NOTIFICATION_ID_NEW_MESSAGES);
|
||||
|
||||
// When called externally, we refresh the sync reports table to pick up
|
||||
// any changes in the account list or account settings
|
||||
|
@ -705,6 +706,6 @@ public class MailService extends Service {
|
|||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(NEW_MESSAGE_NOTIFICATION_ID, notification);
|
||||
notificationManager.notify(NOTIFICATION_ID_NEW_MESSAGES, notification);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package com.android.exchange;
|
||||
|
||||
import com.android.email.SecurityPolicy;
|
||||
import com.android.email.SecurityPolicy.PolicySet;
|
||||
import com.android.email.codec.binary.Base64;
|
||||
import com.android.email.mail.AuthenticationFailedException;
|
||||
import com.android.email.mail.MessagingException;
|
||||
|
@ -964,25 +966,40 @@ public class EasSyncService extends AbstractSyncService {
|
|||
}
|
||||
|
||||
while (!mStop) {
|
||||
userLog("Sending Account syncKey: ", mAccount.mSyncKey);
|
||||
Serializer s = new Serializer();
|
||||
s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
|
||||
.text(mAccount.mSyncKey).end().end().done();
|
||||
HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
|
||||
if (mStop) break;
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
HttpEntity entity = resp.getEntity();
|
||||
int len = (int)entity.getContentLength();
|
||||
if (len != 0) {
|
||||
InputStream is = entity.getContent();
|
||||
// Returns true if we need to sync again
|
||||
if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
|
||||
.parse()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (isAuthError(code)) {
|
||||
userLog("Sending Account syncKey: ", mAccount.mSyncKey);
|
||||
Serializer s = new Serializer();
|
||||
s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
|
||||
.text(mAccount.mSyncKey).end().end().done();
|
||||
HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
|
||||
if (mStop) break;
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
HttpEntity entity = resp.getEntity();
|
||||
int len = (int)entity.getContentLength();
|
||||
if (len != 0) {
|
||||
InputStream is = entity.getContent();
|
||||
// Returns true if we need to sync again
|
||||
if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
|
||||
.parse()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// // EVERYTHING IN THE code==403 BLOCK IS PLACEHOLDER/SAMPLE CODE
|
||||
} else if (code == 403) { // security error
|
||||
// Reality: Find out from server what policies are required
|
||||
// Fake: Hardcode the policies
|
||||
SecurityPolicy sp = SecurityPolicy.getInstance(mContext);
|
||||
PolicySet ps = new PolicySet(4, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, true);
|
||||
// Update the account
|
||||
if (ps.writeAccount(mAccount, "securitySyncKey", true, mContext)) {
|
||||
sp.updatePolicies(mAccount.mId);
|
||||
}
|
||||
// Notify that we are blocked because of policies
|
||||
sp.policiesRequired(mAccount.mId);
|
||||
// and exit (don't sync in this condition)
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
// END PLACEHOLDER CODE
|
||||
} else if (isAuthError(code)) {
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
} else {
|
||||
userLog("FolderSync response error: ", code);
|
||||
|
|
|
@ -107,13 +107,20 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
assertTrue(EMPTY_POLICY_SET.equals(sp.computeAggregatePolicy()));
|
||||
|
||||
// with a single account in security mode, should return same security as in account
|
||||
PolicySet p3in = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 15, 16, false);
|
||||
// first test with partially-populated policies
|
||||
Account a3 = ProviderTestUtils.setupAccount("sec-3", false, mMockContext);
|
||||
p3in.writeAccount(a3, null);
|
||||
a3.save(mMockContext);
|
||||
PolicySet p3out = sp.computeAggregatePolicy();
|
||||
assertNotNull(p3out);
|
||||
assertEquals(p3in, p3out);
|
||||
PolicySet p3ain = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false);
|
||||
p3ain.writeAccount(a3, null, true, mMockContext);
|
||||
PolicySet p3aout = sp.computeAggregatePolicy();
|
||||
assertNotNull(p3aout);
|
||||
assertEquals(p3ain, p3aout);
|
||||
|
||||
// Repeat that test with fully-populated policies
|
||||
PolicySet p3bin = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 15, 16, false);
|
||||
p3bin.writeAccount(a3, null, true, mMockContext);
|
||||
PolicySet p3bout = sp.computeAggregatePolicy();
|
||||
assertNotNull(p3bout);
|
||||
assertEquals(p3bin, p3bout);
|
||||
|
||||
// add another account which mixes it up (some fields will change, others will not)
|
||||
// pw length and pw mode - max logic - will change because larger #s here
|
||||
|
@ -121,8 +128,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
// wipe required - OR logic - will *not* change here because false
|
||||
PolicySet p4in = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false);
|
||||
Account a4 = ProviderTestUtils.setupAccount("sec-4", false, mMockContext);
|
||||
p4in.writeAccount(a4, null);
|
||||
a4.save(mMockContext);
|
||||
p4in.writeAccount(a4, null, true, mMockContext);
|
||||
PolicySet p4out = sp.computeAggregatePolicy();
|
||||
assertNotNull(p4out);
|
||||
assertEquals(20, p4out.mMinPasswordLength);
|
||||
|
@ -137,8 +143,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
// wipe required - OR logic - will change here because true
|
||||
PolicySet p5in = new PolicySet(4, PolicySet.PASSWORD_MODE_NONE, 5, 6, true);
|
||||
Account a5 = ProviderTestUtils.setupAccount("sec-5", false, mMockContext);
|
||||
p5in.writeAccount(a5, null);
|
||||
a5.save(mMockContext);
|
||||
p5in.writeAccount(a5, null, true, mMockContext);
|
||||
PolicySet p5out = sp.computeAggregatePolicy();
|
||||
assertNotNull(p5out);
|
||||
assertEquals(20, p5out.mMinPasswordLength);
|
||||
|
@ -217,7 +222,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
PolicySet p1 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true);
|
||||
Account a = new Account();
|
||||
final String SYNC_KEY = "test_sync_key";
|
||||
p1.writeAccount(a, SYNC_KEY);
|
||||
p1.writeAccount(a, SYNC_KEY, false, null);
|
||||
PolicySet p2 = new PolicySet(a);
|
||||
assertEquals(p1, p2);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue