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 17 package com.android.settings.accounts; 18 19 import static android.content.Intent.EXTRA_USER; 20 21 import android.accounts.AccountManager; 22 import android.accounts.AccountManagerCallback; 23 import android.accounts.AccountManagerFuture; 24 import android.accounts.AuthenticatorException; 25 import android.accounts.OperationCanceledException; 26 import android.app.Activity; 27 import android.app.PendingIntent; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.os.Bundle; 32 import android.os.UserHandle; 33 import android.os.UserManager; 34 import android.util.Log; 35 import android.widget.Toast; 36 37 import com.android.settings.R; 38 import com.android.settings.Settings; 39 import com.android.settings.Utils; 40 import com.android.settings.password.ChooseLockSettingsHelper; 41 42 import java.io.IOException; 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 */ 61 public 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 = "AddAccountSettings"; 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 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 105 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 106 startActivityForResultAsUser( 107 new Intent(intent), ADD_ACCOUNT_REQUEST, mUserHandle); 108 } else { 109 setResult(RESULT_OK); 110 if (mPendingIntent != null) { 111 mPendingIntent.cancel(); 112 mPendingIntent = null; 113 } 114 } 115 116 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "account added: " + bundle); 117 } catch (OperationCanceledException e) { 118 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount was canceled"); 119 } catch (IOException e) { 120 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e); 121 } catch (AuthenticatorException e) { 122 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e); 123 } finally { 124 if (done) { 125 finish(); 126 } 127 } 128 } 129 }; 130 131 private boolean mAddAccountCalled = false; 132 private UserHandle mUserHandle; 133 134 @Override onCreate(Bundle savedInstanceState)135 public void onCreate(Bundle savedInstanceState) { 136 super.onCreate(savedInstanceState); 137 138 if (savedInstanceState != null) { 139 mAddAccountCalled = savedInstanceState.getBoolean(KEY_ADD_CALLED); 140 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "restored"); 141 } 142 143 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); 144 mUserHandle = Utils.getSecureTargetUser(getActivityToken(), um, null /* arguments */, 145 getIntent().getExtras()); 146 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle)) { 147 // We aren't allowed to add an account. 148 Toast.makeText(this, R.string.user_cannot_add_accounts_message, Toast.LENGTH_LONG) 149 .show(); 150 finish(); 151 return; 152 } 153 if (mAddAccountCalled) { 154 // We already called add account - maybe the callback was lost. 155 finish(); 156 return; 157 } 158 if (Utils.startQuietModeDialogIfNecessary(this, um, mUserHandle.getIdentifier())) { 159 finish(); 160 return; 161 } 162 if (um.isUserUnlocked(mUserHandle)) { 163 requestChooseAccount(); 164 } else { 165 // If the user is locked by fbe: we couldn't start the authenticator. So we must ask the 166 // user to unlock it first. 167 final ChooseLockSettingsHelper.Builder builder = 168 new ChooseLockSettingsHelper.Builder(this); 169 final boolean launched = builder.setRequestCode(UNLOCK_WORK_PROFILE_REQUEST) 170 .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title)) 171 .setUserId(mUserHandle.getIdentifier()) 172 .show(); 173 if (!launched) { 174 requestChooseAccount(); 175 } 176 } 177 } 178 179 @Override onActivityResult(int requestCode, int resultCode, Intent data)180 public void onActivityResult(int requestCode, int resultCode, Intent data) { 181 switch (requestCode) { 182 case UNLOCK_WORK_PROFILE_REQUEST: 183 if (resultCode == Activity.RESULT_OK) { 184 requestChooseAccount(); 185 } else { 186 finish(); 187 } 188 break; 189 case CHOOSE_ACCOUNT_REQUEST: 190 if (resultCode == RESULT_CANCELED) { 191 if (data != null) { 192 startActivityAsUser(data, mUserHandle); 193 } 194 setResult(resultCode); 195 finish(); 196 return; 197 } 198 // Go to account setup screen. finish() is called inside mCallback. 199 addAccount(data.getStringExtra(EXTRA_SELECTED_ACCOUNT)); 200 break; 201 case ADD_ACCOUNT_REQUEST: 202 setResult(resultCode); 203 if (mPendingIntent != null) { 204 mPendingIntent.cancel(); 205 mPendingIntent = null; 206 } 207 finish(); 208 break; 209 } 210 } 211 212 @Override onSaveInstanceState(Bundle outState)213 protected void onSaveInstanceState(Bundle outState) { 214 super.onSaveInstanceState(outState); 215 outState.putBoolean(KEY_ADD_CALLED, mAddAccountCalled); 216 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "saved"); 217 } 218 requestChooseAccount()219 private void requestChooseAccount() { 220 final String[] authorities = 221 getIntent().getStringArrayExtra(AccountPreferenceBase.AUTHORITIES_FILTER_KEY); 222 final String[] accountTypes = 223 getIntent().getStringArrayExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY); 224 final Intent intent = new Intent(this, Settings.ChooseAccountActivity.class); 225 if (authorities != null) { 226 intent.putExtra(AccountPreferenceBase.AUTHORITIES_FILTER_KEY, authorities); 227 } 228 if (accountTypes != null) { 229 intent.putExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY, accountTypes); 230 } 231 intent.putExtra(EXTRA_USER, mUserHandle); 232 startActivityForResult(intent, CHOOSE_ACCOUNT_REQUEST); 233 } 234 addAccount(String accountType)235 private void addAccount(String accountType) { 236 Bundle addAccountOptions = new Bundle(); 237 /* 238 * The identityIntent is for the purposes of establishing the identity 239 * of the caller and isn't intended for launching activities, services 240 * or broadcasts. 241 * 242 * Unfortunately for legacy reasons we still need to support this. But 243 * we can disable the intent so that 3rd party authenticators can't 244 * fill in addressing information and launch arbitrary actions. 245 */ 246 Intent identityIntent = new Intent(); 247 identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE)); 248 identityIntent.setAction(SHOULD_NOT_RESOLVE); 249 identityIntent.addCategory(SHOULD_NOT_RESOLVE); 250 251 mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 252 PendingIntent.FLAG_IMMUTABLE); 253 addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent); 254 addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this)); 255 AccountManager.get(this).addAccountAsUser( 256 accountType, 257 null, /* authTokenType */ 258 null, /* requiredFeatures */ 259 addAccountOptions, 260 null, 261 mCallback, 262 null /* handler */, 263 mUserHandle); 264 mAddAccountCalled = true; 265 } 266 } 267