DO NOT MERGE Workaround for KeyguardLock problem
* The device policies that enforce the use of a device PIN or password can be sidestepped by apps that implement KeyguardManager.KeyguardLock * This renders the policies unuseable * To prevent this, the email app now scans for any packages holding the DISABLE_KEYGUARD permission. The existence of any non-system app with this permission will put all security-enabled EAS accounts into a security hold, and post a dialog describing the problem. * The user must uninstall any such app(s) in order to sync their EAS data. Bug: 2737842 Change-Id: I4c96d76b12d9242b5c755dd60d7578a825fae597
This commit is contained in:
parent
27e75533e9
commit
3ee0cad5f5
@ -235,6 +235,13 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.DEVICE_STORAGE_OK" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_ADDED" />
|
||||
<action android:name="android.intent.action.PACKAGE_CHANGED" />
|
||||
<action android:name="android.intent.action.PACKAGE_REMOVED" />
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
<!-- Support for DeviceAdmin / DevicePolicyManager. See SecurityPolicy class for impl. -->
|
||||
|
@ -681,6 +681,12 @@
|
||||
<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>
|
||||
<!-- Title of keyguard-lock-disabled dialog box -->
|
||||
<string name="keyguard_disabled_dlg_title">Screen lock disabled</string>
|
||||
<!-- Message of keyguard-lock-disabled dialog box -->
|
||||
<string name="keyguard_disabled_dlg_message_fmt">This account cannot be used while the
|
||||
application \"<xliff:g id="application">%s</xliff:g>\" is installed, because it
|
||||
interferes with the operation of the screen lock.</string>
|
||||
|
||||
<!-- Notification message in notifications window when calendar sync is
|
||||
automatically enabled for pre-existing Exchange accounts on upgrade -->
|
||||
|
@ -250,6 +250,12 @@ public class Email extends Application {
|
||||
*/
|
||||
MailService.actionReschedule(context);
|
||||
}
|
||||
// If enabling, clear the cached test for keyguard-disabling apps; This is because
|
||||
// we may have missed package install/replace/remove broadcasts while we had no accounts
|
||||
// and the BootReceiver was disabled.
|
||||
if (enabled) {
|
||||
SecurityPolicy.getInstance(context.getApplicationContext()).invalidateKeyguardCache();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -33,11 +33,16 @@ import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility functions to support reading and writing security policies, and handshaking the device
|
||||
* into and out of various security states.
|
||||
@ -49,6 +54,10 @@ public class SecurityPolicy {
|
||||
private DevicePolicyManager mDPM;
|
||||
private ComponentName mAdminName;
|
||||
private PolicySet mAggregatePolicy;
|
||||
// false = unknown, true = mKeyguardDisablePackageName is valid (or null)
|
||||
private boolean mKeyguardDisableChecked;
|
||||
// null = no apps disabling, non-null = one or more apps disabling
|
||||
private String mKeyguardDisablePackageName;
|
||||
|
||||
/* package */ static final PolicySet NO_POLICY_SET =
|
||||
new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
|
||||
@ -101,6 +110,7 @@ public class SecurityPolicy {
|
||||
mDPM = null;
|
||||
mAdminName = new ComponentName(context, PolicyAdmin.class);
|
||||
mAggregatePolicy = null;
|
||||
mKeyguardDisableChecked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,6 +275,13 @@ public class SecurityPolicy {
|
||||
}
|
||||
}
|
||||
if (policies.mPasswordMode > 0) {
|
||||
// If the server requests a password policy of any kind (PIN, password, etc)
|
||||
// and the user has a keyguard-disable package, then we cannot support the
|
||||
// policy. In a rare case of a server that doesn't enforce PIN/Password, we
|
||||
// won't do this test.
|
||||
if (getKeyguardDisablePackage() != null) {
|
||||
return false;
|
||||
}
|
||||
if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
|
||||
return false;
|
||||
}
|
||||
@ -610,6 +627,55 @@ public class SecurityPolicy {
|
||||
return dpm.isAdminActive(mAdminName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the cached result of checking for lock screen disablers. Called by package
|
||||
* add/replace/delete broadcasts.
|
||||
*/
|
||||
public synchronized void invalidateKeyguardCache() {
|
||||
mKeyguardDisableChecked = false;
|
||||
mKeyguardDisablePackageName = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of any app that is disabling the keyguard (lock screen) - in which case,
|
||||
* we must treat the device as if required lock screen policies are not met.
|
||||
*
|
||||
* @return non-null if any app is disabling the keyguard
|
||||
*/
|
||||
public synchronized String getKeyguardDisablePackage() {
|
||||
if (!mKeyguardDisableChecked) {
|
||||
mKeyguardDisablePackageName = findKeyguardPermission();
|
||||
mKeyguardDisableChecked = true;
|
||||
}
|
||||
return mKeyguardDisablePackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan installed packages and look for DISABLE_KEYGUARD permission. Return the first
|
||||
* installed packages (if any are found). This won't be cheap, so do it as little as possible.
|
||||
*
|
||||
* @return the display name of the package, or null if all clear
|
||||
*/
|
||||
private String findKeyguardPermission() {
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
List<PackageInfo> packages = pm.getInstalledPackages(0);
|
||||
for (PackageInfo info : packages) {
|
||||
// skip over system packages; they are allowed to have the permission
|
||||
if (0 != (info.applicationInfo.flags &
|
||||
(ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP))) {
|
||||
continue;
|
||||
}
|
||||
// all other packages, check for specific permission
|
||||
int permissionResult = pm.checkPermission(android.Manifest.permission.DISABLE_KEYGUARD,
|
||||
info.packageName);
|
||||
if (permissionResult == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(Email.LOG_TAG, "DISABLE_KEYGUARD found in " + info.packageName);
|
||||
return info.applicationInfo.loadLabel(pm).toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report admin component name - for making calls into device policy manager
|
||||
*/
|
||||
|
@ -21,8 +21,11 @@ import com.android.email.SecurityPolicy;
|
||||
import com.android.email.provider.EmailContent.Account;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
@ -40,8 +43,12 @@ public class AccountSecurity extends Activity {
|
||||
|
||||
private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity.setup.ACCOUNT_ID";
|
||||
|
||||
private static final int DIALOG_KEYGUARD_DISABLED = 1;
|
||||
|
||||
private static final int REQUEST_ENABLE = 1;
|
||||
|
||||
private String mKeyguardBlockerName;
|
||||
|
||||
/**
|
||||
* Used for generating intent for this activity (which is intended to be launched
|
||||
* from a notification.)
|
||||
@ -59,6 +66,11 @@ public class AccountSecurity extends Activity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
boolean finishNow = true;
|
||||
// Return RESULT_OK in all cases unless explicitly changed (see below). This is ignored
|
||||
// when we're launched from notification, but it's checked when we're launched from
|
||||
// (and return to) AccountSetupNames.
|
||||
setResult(Activity.RESULT_OK);
|
||||
|
||||
Intent i = getIntent();
|
||||
long accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1);
|
||||
@ -68,7 +80,8 @@ public class AccountSecurity extends Activity {
|
||||
// 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) {
|
||||
if ((account.mSecurityFlags != 0) ||
|
||||
(0 != (account.mFlags & Account.FLAGS_SECURITY_HOLD))) {
|
||||
// This account wants to control security
|
||||
if (!security.isActiveAdmin()) {
|
||||
// try to become active - must happen here in this activity, to get result
|
||||
@ -83,12 +96,14 @@ public class AccountSecurity extends Activity {
|
||||
return;
|
||||
} else {
|
||||
// already active - try to set actual policies, finish, and return
|
||||
setActivePolicies();
|
||||
finishNow = setActivePolicies();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finish();
|
||||
if (finishNow) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,11 +111,12 @@ public class AccountSecurity extends Activity {
|
||||
*/
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
boolean finishNow = true;
|
||||
switch (requestCode) {
|
||||
case REQUEST_ENABLE:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// now active - try to set actual policies
|
||||
setActivePolicies();
|
||||
finishNow = setActivePolicies();
|
||||
} else {
|
||||
// failed - repost notification, and exit
|
||||
final long accountId = getIntent().getLongExtra(EXTRA_ACCOUNT_ID, -1);
|
||||
@ -115,31 +131,85 @@ public class AccountSecurity extends Activity {
|
||||
}
|
||||
}
|
||||
}
|
||||
finish();
|
||||
// This activity has no layout and in most cases serves only as a dispatcher for
|
||||
// checking and adjusting device security policies. However, in a few cases, it
|
||||
// presents an error dialog. In those cases, setActivePolicies() will return false, and
|
||||
// we don't call finish() so the dialog is able to display.
|
||||
if (finishNow) {
|
||||
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.
|
||||
*
|
||||
* @return true if OK to finish the activity (false if we posted a dialog)
|
||||
*/
|
||||
private void setActivePolicies() {
|
||||
private boolean setActivePolicies() {
|
||||
SecurityPolicy sp = SecurityPolicy.getInstance(this);
|
||||
// check current security level - if sufficient, we're done!
|
||||
if (sp.isActive(null)) {
|
||||
sp.clearAccountHoldFlags();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// set current security level
|
||||
sp.setActivePolicies();
|
||||
// check current security level - if sufficient, we're done!
|
||||
if (sp.isActive(null)) {
|
||||
sp.clearAccountHoldFlags();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// if the problem is an app blocking the lock screen, notify the user
|
||||
String keyguardBlocker = sp.getKeyguardDisablePackage();
|
||||
if (keyguardBlocker != null) {
|
||||
mKeyguardBlockerName = keyguardBlocker;
|
||||
showDialog(DIALOG_KEYGUARD_DISABLED);
|
||||
// Set activity to return RESULT_CANCELED (after dialog) to notify caller that we
|
||||
// didn't configure policies completely.
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
return false;
|
||||
}
|
||||
// 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);
|
||||
// TODO: use startActivityForResult, and when we see the result, recheck to see
|
||||
// if the user actually entered a sufficient password. If not, repost the notification.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_KEYGUARD_DISABLED:
|
||||
return new AlertDialog.Builder(this)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(R.string.keyguard_disabled_dlg_title)
|
||||
.setMessage(getString(R.string.keyguard_disabled_dlg_message_fmt,
|
||||
mKeyguardBlockerName))
|
||||
.setNeutralButton(R.string.okay_action, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
dismissDialog(DIALOG_KEYGUARD_DISABLED);
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
}
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a cached dialog with current values (e.g. account name)
|
||||
*/
|
||||
@Override
|
||||
public void onPrepareDialog(int id, Dialog dialog) {
|
||||
switch (id) {
|
||||
case DIALOG_KEYGUARD_DISABLED:
|
||||
AlertDialog alert = (AlertDialog) dialog;
|
||||
alert.setMessage(getString(R.string.keyguard_disabled_dlg_message_fmt,
|
||||
mKeyguardBlockerName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package com.android.email.activity.setup;
|
||||
import com.android.email.AccountBackupRestore;
|
||||
import com.android.email.R;
|
||||
import com.android.email.Utility;
|
||||
import com.android.email.activity.AccountFolderList;
|
||||
import com.android.email.activity.Welcome;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.Account;
|
||||
@ -146,12 +147,25 @@ public class AccountSetupNames extends Activity implements OnClickListener {
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
onBackPressed(true); // OK to "proceed" to next step (the account has been created)
|
||||
}
|
||||
|
||||
private void onBackPressed(boolean okToProceed) {
|
||||
boolean easFlowMode = getIntent().getBooleanExtra(EXTRA_EAS_FLOW, false);
|
||||
if (easFlowMode) {
|
||||
AccountSetupBasics.actionAccountCreateFinishedEas(this);
|
||||
} else {
|
||||
if (mAccount != null) {
|
||||
AccountSetupBasics.actionAccountCreateFinished(this, mAccount.mId);
|
||||
if (okToProceed) {
|
||||
AccountSetupBasics.actionAccountCreateFinished(this, mAccount.mId);
|
||||
} else {
|
||||
// This is what we do if the account was created, but somebody called
|
||||
// onBackPressed(false), indicating that the new account's inbox should not be
|
||||
// entered. In this case, we'll just go back to the accounts list.
|
||||
// We don't use the typical "Welcome" activity because if there is only one
|
||||
// account, it will also try to visit the inbox of that account.
|
||||
AccountFolderList.actionShowAccounts(this);
|
||||
}
|
||||
} else {
|
||||
// Safety check here; If mAccount is null (due to external issues or bugs)
|
||||
// just rewind back to Welcome, which can handle any configuration of accounts
|
||||
@ -249,14 +263,15 @@ public class AccountSetupNames extends Activity implements OnClickListener {
|
||||
|
||||
/**
|
||||
* Handle the eventual result from the security update activity
|
||||
*
|
||||
* TODO: If the user doesn't update the security, don't go to the MessageList.
|
||||
*/
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_SECURITY:
|
||||
onBackPressed();
|
||||
// If we get RESULT_CANCEL, we tell onBackPressed that there was an error,
|
||||
// and it won't try to take us to the MessageList of the new account.
|
||||
onBackPressed(resultCode == Activity.RESULT_OK);
|
||||
break;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package com.android.email.service;
|
||||
|
||||
import com.android.email.AccountBackupRestore;
|
||||
import com.android.email.Email;
|
||||
import com.android.email.SecurityPolicy;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@ -29,17 +30,24 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
// Restore accounts, if it has not happened already
|
||||
AccountBackupRestore.restoreAccountsIfNeeded(context);
|
||||
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
String intentAction = intent.getAction();
|
||||
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
|
||||
// Returns true if there are any accounts
|
||||
if (Email.setServicesEnabled(context)) {
|
||||
MailService.actionReschedule(context);
|
||||
}
|
||||
}
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intentAction)) {
|
||||
MailService.actionCancel(context);
|
||||
}
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intentAction)) {
|
||||
MailService.actionReschedule(context);
|
||||
}
|
||||
else if (Intent.ACTION_PACKAGE_ADDED.equals(intentAction) ||
|
||||
Intent.ACTION_PACKAGE_REPLACED.equals(intentAction) ||
|
||||
Intent.ACTION_PACKAGE_REMOVED.equals(intentAction)) {
|
||||
SecurityPolicy.getInstance(context.getApplicationContext()).invalidateKeyguardCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user