AccountSecurity.java revision 45bfac00e58059eed71fd22e2767c8e0437b6251
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity.setup;
18
19import com.android.email.R;
20import com.android.email.SecurityPolicy;
21import com.android.email.Utility;
22import com.android.email.activity.ActivityHelper;
23import com.android.email.provider.EmailContent.Account;
24import com.android.email.provider.EmailContent.HostAuth;
25
26import android.app.Activity;
27import android.app.admin.DevicePolicyManager;
28import android.content.Context;
29import android.content.Intent;
30import android.os.AsyncTask;
31import android.os.Bundle;
32
33/**
34 * Psuedo-activity (no UI) to bootstrap the user up to a higher desired security level.  This
35 * bootstrap requires the following steps.
36 *
37 * 1.  Confirm the account of interest has any security policies defined - exit early if not
38 * 2.  If not actively administrating the device, ask Device Policy Manager to start that
39 * 3.  When we are actively administrating, check current policies and see if they're sufficient
40 * 4.  If not, set policies
41 * 5.  If necessary, request for user to update device password
42 * 6.  If necessary, request for user to activate device encryption
43 */
44public class AccountSecurity extends Activity {
45
46    private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity.setup.ACCOUNT_ID";
47
48    private static final int REQUEST_ENABLE = 1;
49    private static final int REQUEST_PASSWORD = 2;
50    private static final int REQUEST_ENCRYPTION = 3;
51
52    private boolean mTriedAddAdministrator = false;
53    private boolean mTriedSetPassword = false;
54    private boolean mTriedSetEncryption = false;
55    private Account mAccount;
56
57    /**
58     * Used for generating intent for this activity (which is intended to be launched
59     * from a notification.)
60     *
61     * @param context Calling context for building the intent
62     * @param accountId The account of interest
63     * @return an Intent which can be used to view that account
64     */
65    public static Intent actionUpdateSecurityIntent(Context context, long accountId) {
66        Intent intent = new Intent(context, AccountSecurity.class);
67        intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
68        return intent;
69    }
70
71    @Override
72    public void onCreate(Bundle savedInstanceState) {
73        super.onCreate(savedInstanceState);
74        ActivityHelper.debugSetWindowFlags(this);
75
76        Intent i = getIntent();
77        final long accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1);
78        SecurityPolicy security = SecurityPolicy.getInstance(this);
79        security.clearNotification(accountId);
80        if (accountId == -1) {
81            finish();
82            return;
83        }
84
85        // Let onCreate exit, while background thread retrieves account.
86        // Then start the security check/bootstrap process.
87        new AsyncTask<Void, Void, Account>() {
88            @Override
89            protected Account doInBackground(Void... params) {
90                return Account.restoreAccountWithId(AccountSecurity.this, accountId);
91            }
92
93            @Override
94            protected void onPostExecute(Account result) {
95                mAccount = result;
96                if (mAccount != null && mAccount.mSecurityFlags != 0) {
97                    // This account wants to control security
98                    tryAdvanceSecurity(mAccount);
99                    return;
100                }
101                finish();
102            }
103        }.execute();
104    }
105
106    /**
107     * After any of the activities return, try to advance to the "next step"
108     */
109    @Override
110    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
111        tryAdvanceSecurity(mAccount);
112        super.onActivityResult(requestCode, resultCode, data);
113    }
114
115    /**
116     * Walk the user through the required steps to become an active administrator and with
117     * the requisite security settings for the given account.
118     *
119     * These steps will be repeated each time we return from a given attempt (e.g. asking the
120     * user to choose a device pin/password).  In a typical activation, we may repeat these
121     * steps a few times.  It may go as far as step 5 (password) or step 6 (encryption), but it
122     * will terminate when step 2 (isActive()) succeeds.
123     *
124     * If at any point we do not advance beyond a given user step, (e.g. the user cancels
125     * instead of setting a password) we simply repost the security notification, and exit.
126     * We never want to loop here.
127     */
128    private void tryAdvanceSecurity(Account account) {
129        SecurityPolicy security = SecurityPolicy.getInstance(this);
130
131        // Step 1.  Check if we are an active device administrator, and stop here to activate
132        if (!security.isActiveAdmin()) {
133            if (mTriedAddAdministrator) {
134                repostNotification(account, security);
135                finish();
136            } else {
137                mTriedAddAdministrator = true;
138                // retrieve name of server for the format string
139                HostAuth hostAuth = HostAuth.restoreHostAuthWithId(this, account.mHostAuthKeyRecv);
140                if (hostAuth == null) {
141                    repostNotification(account, security);
142                    finish();
143                } else {
144                    // try to become active - must happen here in activity, to get result
145                    Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
146                    intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
147                            security.getAdminComponent());
148                    intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
149                            this.getString(R.string.account_security_policy_explanation_fmt,
150                                    hostAuth.mAddress));
151                    startActivityForResult(intent, REQUEST_ENABLE);
152                }
153            }
154            return;
155        }
156
157        // Step 2.  Check if the current aggregate security policy is being satisfied by the
158        // DevicePolicyManager (the current system security level).
159        if (security.isActive(null)) {
160            Account.clearSecurityHoldOnAllAccounts(this);
161            finish();
162            return;
163        }
164
165        // Step 3.  Try to assert the current aggregate security requirements with the system.
166        security.setActivePolicies();
167
168        // Step 4.  Recheck the security policy, and determine what changes are needed (if any)
169        // to satisfy the requirements.
170        int inactiveReasons = security.getInactiveReasons(null);
171
172        // Step 5.  If password is needed, try to have the user set it
173        if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_PASSWORD) != 0) {
174            if (mTriedSetPassword) {
175                repostNotification(account, security);
176                finish();
177            } else {
178                mTriedSetPassword = true;
179                // launch the activity to have the user set a new password.
180                Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
181                startActivityForResult(intent, REQUEST_PASSWORD);
182            }
183            return;
184        }
185
186        // Step 6.  If encryption is needed, try to have the user set it
187        if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_ENCRYPTION) != 0) {
188            if (mTriedSetEncryption) {
189                repostNotification(account, security);
190                finish();
191            } else {
192                mTriedSetEncryption = true;
193                // launch the activity to start up encryption.
194                Intent intent = new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION);
195                startActivityForResult(intent, REQUEST_ENCRYPTION);
196            }
197            return;
198        }
199
200        // Step 7.  No problems were found, so clear holds and exit
201        Account.clearSecurityHoldOnAllAccounts(this);
202        finish();
203    }
204
205    /**
206     * Mark an account as not-ready-for-sync and post a notification to bring the user back here
207     * eventually.
208     */
209    private void repostNotification(final Account account, final SecurityPolicy security) {
210        Utility.runAsync(new Runnable() {
211            @Override
212            public void run() {
213                security.policiesRequired(account.mId);
214            }
215        });
216    }
217}
218