1/*
2 * Copyright (C) 2008 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.settings.accounts;
18
19import android.accounts.AccountManager;
20import android.accounts.AccountManagerCallback;
21import android.accounts.AccountManagerFuture;
22import android.accounts.AuthenticatorException;
23import android.accounts.OperationCanceledException;
24import android.app.Activity;
25import android.app.PendingIntent;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.os.Bundle;
30import android.os.UserHandle;
31import android.os.UserManager;
32import android.util.Log;
33import android.widget.Toast;
34
35import com.android.settings.ChooseLockSettingsHelper;
36import com.android.settings.R;
37import com.android.settings.Settings;
38import com.android.settings.Utils;
39
40import java.io.IOException;
41
42import static android.content.Intent.EXTRA_USER;
43/**
44 * Entry point Activity for account setup. Works as follows
45 *
46 * 1) When the other Activities launch this Activity, it launches {@link ChooseAccountActivity}
47 *    without showing anything.
48 * 2) After receiving an account type from ChooseAccountActivity, this Activity launches the
49 *    account setup specified by AccountManager.
50 * 3) After the account setup, this Activity finishes without showing anything.
51 *
52 * Note:
53 * Previously this Activity did what {@link ChooseAccountActivity} does right now, but we
54 * currently delegate the work to the other Activity. When we let this Activity do that work, users
55 * would see the list of account types when leaving this Activity, since the UI is already ready
56 * when returning from each account setup, which doesn't look good.
57 *
58 * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
59 * which the action needs to be performed is different to the one the Settings App will run in.
60 */
61public class AddAccountSettings extends Activity {
62    /**
63     *
64     */
65    private static final String KEY_ADD_CALLED = "AddAccountCalled";
66
67    /**
68     * Extra parameter to identify the caller. Applications may display a
69     * different UI if the calls is made from Settings or from a specific
70     * application.
71     */
72    private static final String KEY_CALLER_IDENTITY = "pendingIntent";
73    private static final String SHOULD_NOT_RESOLVE = "SHOULDN'T RESOLVE!";
74
75    private static final String TAG = "AccountSettings";
76
77    /* package */ static final String EXTRA_SELECTED_ACCOUNT = "selected_account";
78
79    // show additional info regarding the use of a device with multiple users
80    static final String EXTRA_HAS_MULTIPLE_USERS = "hasMultipleUsers";
81
82    private static final int CHOOSE_ACCOUNT_REQUEST = 1;
83    private static final int ADD_ACCOUNT_REQUEST = 2;
84    private static final int UNLOCK_WORK_PROFILE_REQUEST = 3;
85
86    private PendingIntent mPendingIntent;
87
88    private final AccountManagerCallback<Bundle> mCallback = new AccountManagerCallback<Bundle>() {
89        @Override
90        public void run(AccountManagerFuture<Bundle> future) {
91            boolean done = true;
92            try {
93                Bundle bundle = future.getResult();
94                //bundle.keySet();
95                Intent intent = (Intent) bundle.get(AccountManager.KEY_INTENT);
96                if (intent != null) {
97                    done = false;
98                    Bundle addAccountOptions = new Bundle();
99                    addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
100                    addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS,
101                            Utils.hasMultipleUsers(AddAccountSettings.this));
102                    addAccountOptions.putParcelable(EXTRA_USER, mUserHandle);
103                    intent.putExtras(addAccountOptions);
104                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
105                    startActivityForResultAsUser(intent, ADD_ACCOUNT_REQUEST, mUserHandle);
106                } else {
107                    setResult(RESULT_OK);
108                    if (mPendingIntent != null) {
109                        mPendingIntent.cancel();
110                        mPendingIntent = null;
111                    }
112                }
113
114                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "account added: " + bundle);
115            } catch (OperationCanceledException e) {
116                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount was canceled");
117            } catch (IOException e) {
118                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e);
119            } catch (AuthenticatorException e) {
120                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e);
121            } finally {
122                if (done) {
123                    finish();
124                }
125            }
126        }
127    };
128
129    private boolean mAddAccountCalled = false;
130    private UserHandle mUserHandle;
131
132    @Override
133    public void onCreate(Bundle savedInstanceState) {
134        super.onCreate(savedInstanceState);
135
136        if (savedInstanceState != null) {
137            mAddAccountCalled = savedInstanceState.getBoolean(KEY_ADD_CALLED);
138            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "restored");
139        }
140
141        final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
142        mUserHandle = Utils.getSecureTargetUser(getActivityToken(), um, null /* arguments */,
143                getIntent().getExtras());
144        if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle)) {
145            // We aren't allowed to add an account.
146            Toast.makeText(this, R.string.user_cannot_add_accounts_message, Toast.LENGTH_LONG)
147                    .show();
148            finish();
149            return;
150        }
151        if (mAddAccountCalled) {
152            // We already called add account - maybe the callback was lost.
153            finish();
154            return;
155        }
156        if (Utils.startQuietModeDialogIfNecessary(this, um, mUserHandle.getIdentifier())) {
157            finish();
158            return;
159        }
160        if (um.isUserUnlocked(mUserHandle)) {
161            requestChooseAccount();
162        } else {
163            // If the user is locked by fbe: we couldn't start the authenticator. So we must ask the
164            // user to unlock it first.
165            ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
166            if (!helper.launchConfirmationActivity(UNLOCK_WORK_PROFILE_REQUEST,
167                    getString(R.string.unlock_set_unlock_launch_picker_title),
168                    false,
169                    mUserHandle.getIdentifier())) {
170                requestChooseAccount();
171            }
172        }
173    }
174
175    @Override
176    public void onActivityResult(int requestCode, int resultCode, Intent data) {
177        switch (requestCode) {
178        case UNLOCK_WORK_PROFILE_REQUEST:
179            if (resultCode == Activity.RESULT_OK) {
180                requestChooseAccount();
181            } else {
182                finish();
183            }
184            break;
185        case CHOOSE_ACCOUNT_REQUEST:
186            if (resultCode == RESULT_CANCELED) {
187                if (data != null) {
188                    startActivityAsUser(data, mUserHandle);
189                }
190                setResult(resultCode);
191                finish();
192                return;
193            }
194            // Go to account setup screen. finish() is called inside mCallback.
195            addAccount(data.getStringExtra(EXTRA_SELECTED_ACCOUNT));
196            break;
197        case ADD_ACCOUNT_REQUEST:
198            setResult(resultCode);
199            if (mPendingIntent != null) {
200                mPendingIntent.cancel();
201                mPendingIntent = null;
202            }
203            finish();
204            break;
205        }
206    }
207
208    @Override
209    protected void onSaveInstanceState(Bundle outState) {
210        super.onSaveInstanceState(outState);
211        outState.putBoolean(KEY_ADD_CALLED, mAddAccountCalled);
212        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "saved");
213    }
214
215    private void requestChooseAccount() {
216        final String[] authorities =
217                getIntent().getStringArrayExtra(AccountPreferenceBase.AUTHORITIES_FILTER_KEY);
218        final String[] accountTypes =
219                getIntent().getStringArrayExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY);
220        final Intent intent = new Intent(this, Settings.ChooseAccountActivity.class);
221        if (authorities != null) {
222            intent.putExtra(AccountPreferenceBase.AUTHORITIES_FILTER_KEY, authorities);
223        }
224        if (accountTypes != null) {
225            intent.putExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY, accountTypes);
226        }
227        intent.putExtra(EXTRA_USER, mUserHandle);
228        startActivityForResult(intent, CHOOSE_ACCOUNT_REQUEST);
229    }
230
231    private void addAccount(String accountType) {
232        Bundle addAccountOptions = new Bundle();
233        /*
234         * The identityIntent is for the purposes of establishing the identity
235         * of the caller and isn't intended for launching activities, services
236         * or broadcasts.
237         *
238         * Unfortunately for legacy reasons we still need to support this. But
239         * we can cripple the intent so that 3rd party authenticators can't
240         * fill in addressing information and launch arbitrary actions.
241         */
242        Intent identityIntent = new Intent();
243        identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));
244        identityIntent.setAction(SHOULD_NOT_RESOLVE);
245        identityIntent.addCategory(SHOULD_NOT_RESOLVE);
246
247        mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 0);
248        addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
249        addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
250        AccountManager.get(this).addAccountAsUser(
251                accountType,
252                null, /* authTokenType */
253                null, /* requiredFeatures */
254                addAccountOptions,
255                null,
256                mCallback,
257                null /* handler */,
258                mUserHandle);
259        mAddAccountCalled  = true;
260    }
261}
262