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.email.activity.setup; 18 19import android.app.Activity; 20import android.content.ContentValues; 21import android.content.Context; 22import android.content.Intent; 23import android.net.Uri; 24import android.os.AsyncTask; 25import android.os.Bundle; 26import android.provider.ContactsContract.Profile; 27import android.text.Editable; 28import android.text.TextUtils; 29import android.text.TextWatcher; 30import android.text.method.TextKeyListener; 31import android.text.method.TextKeyListener.Capitalize; 32import android.view.View; 33import android.view.View.OnClickListener; 34import android.widget.Button; 35import android.widget.EditText; 36 37import com.android.email.R; 38import com.android.email.activity.ActivityHelper; 39import com.android.email.activity.UiUtilities; 40import com.android.email.activity.Welcome; 41import com.android.email.provider.AccountBackupRestore; 42import com.android.emailcommon.provider.Account; 43import com.android.emailcommon.provider.EmailContent.AccountColumns; 44import com.android.emailcommon.provider.HostAuth; 45import com.android.emailcommon.utility.EmailAsyncTask; 46import com.android.emailcommon.utility.Utility; 47 48/** 49 * Final screen of setup process. Collect account nickname and/or username. 50 */ 51public class AccountSetupNames extends AccountSetupActivity implements OnClickListener { 52 private static final int REQUEST_SECURITY = 0; 53 54 private static final Uri PROFILE_URI = Profile.CONTENT_URI; 55 56 private EditText mDescription; 57 private EditText mName; 58 private Button mNextButton; 59 private boolean mEasAccount = false; 60 61 public static void actionSetNames(Activity fromActivity) { 62 fromActivity.startActivity(new Intent(fromActivity, AccountSetupNames.class)); 63 } 64 65 @Override 66 public void onCreate(Bundle savedInstanceState) { 67 super.onCreate(savedInstanceState); 68 ActivityHelper.debugSetWindowFlags(this); 69 setContentView(R.layout.account_setup_names); 70 mDescription = (EditText) UiUtilities.getView(this, R.id.account_description); 71 mName = (EditText) UiUtilities.getView(this, R.id.account_name); 72 View accountNameLabel = UiUtilities.getView(this, R.id.account_name_label); 73 mNextButton = (Button) UiUtilities.getView(this, R.id.next); 74 mNextButton.setOnClickListener(this); 75 76 TextWatcher validationTextWatcher = new TextWatcher() { 77 @Override 78 public void afterTextChanged(Editable s) { 79 validateFields(); 80 } 81 82 @Override 83 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 84 } 85 86 @Override 87 public void onTextChanged(CharSequence s, int start, int before, int count) { 88 } 89 }; 90 mName.addTextChangedListener(validationTextWatcher); 91 mName.setKeyListener(TextKeyListener.getInstance(false, Capitalize.WORDS)); 92 93 Account account = SetupData.getAccount(); 94 if (account == null) { 95 throw new IllegalStateException("unexpected null account"); 96 } 97 if (account.mHostAuthRecv == null) { 98 throw new IllegalStateException("unexpected null hostauth"); 99 } 100 int flowMode = SetupData.getFlowMode(); 101 102 if (flowMode != SetupData.FLOW_MODE_FORCE_CREATE 103 && flowMode != SetupData.FLOW_MODE_EDIT) { 104 String accountEmail = account.mEmailAddress; 105 mDescription.setText(accountEmail); 106 107 // Move cursor to the end so it's easier to erase in case the user doesn't like it. 108 mDescription.setSelection(accountEmail.length()); 109 } 110 111 // Remember whether we're an EAS account, since it doesn't require the user name field 112 mEasAccount = HostAuth.SCHEME_EAS.equals(account.mHostAuthRecv.mProtocol); 113 if (mEasAccount) { 114 mName.setVisibility(View.GONE); 115 accountNameLabel.setVisibility(View.GONE); 116 } else { 117 if (account != null && account.getSenderName() != null) { 118 mName.setText(account.getSenderName()); 119 } else if (flowMode != SetupData.FLOW_MODE_FORCE_CREATE 120 && flowMode != SetupData.FLOW_MODE_EDIT) { 121 // Attempt to prefill the name field from the profile if we don't have it, 122 prefillNameFromProfile(); 123 } 124 } 125 126 // Make sure the "done" button is in the proper state 127 validateFields(); 128 129 // Proceed immediately if in account creation mode 130 if (flowMode == SetupData.FLOW_MODE_FORCE_CREATE) { 131 onNext(); 132 } 133 } 134 135 private void prefillNameFromProfile() { 136 new EmailAsyncTask<Void, Void, String>(null) { 137 @Override 138 protected String doInBackground(Void... params) { 139 String[] projection = new String[] { Profile.DISPLAY_NAME }; 140 return Utility.getFirstRowString( 141 AccountSetupNames.this, PROFILE_URI, projection, null, null, null, 0); 142 } 143 144 @Override 145 public void onSuccess(String result) { 146 // Views can only be modified on the main thread. 147 mName.setText(result); 148 } 149 }.executeParallel((Void[]) null); 150 } 151 152 /** 153 * Implements OnClickListener 154 */ 155 @Override 156 public void onClick(View v) { 157 switch (v.getId()) { 158 case R.id.next: 159 onNext(); 160 break; 161 } 162 } 163 164 /** 165 * Check input fields for legal values and enable/disable next button 166 */ 167 private void validateFields() { 168 boolean enableNextButton = true; 169 // Validation is based only on the "user name" field, not shown for EAS accounts 170 if (!mEasAccount) { 171 String userName = mName.getText().toString().trim(); 172 if (TextUtils.isEmpty(userName)) { 173 enableNextButton = false; 174 mName.setError(getString(R.string.account_setup_names_user_name_empty_error)); 175 } else { 176 mName.setError(null); 177 } 178 } 179 mNextButton.setEnabled(enableNextButton); 180 } 181 182 /** 183 * Block the back key if we are currently processing the "next" key" 184 */ 185 @Override 186 public void onBackPressed() { 187 if (mNextButton.isEnabled()) { 188 finishActivity(); 189 } 190 } 191 192 private void finishActivity() { 193 if (SetupData.getFlowMode() != SetupData.FLOW_MODE_NORMAL) { 194 AccountSetupBasics.actionAccountCreateFinishedAccountFlow(this); 195 } else { 196 Account account = SetupData.getAccount(); 197 if (account != null) { 198 AccountSetupBasics.actionAccountCreateFinished(this, account.mId); 199 } else { 200 // Safety check here; If mAccount is null (due to external issues or bugs) 201 // just rewind back to Welcome, which can handle any configuration of accounts 202 Welcome.actionStart(this); 203 } 204 } 205 finish(); 206 } 207 208 /** 209 * After clicking the next button, we'll start an async task to commit the data 210 * and other steps to finish the creation of the account. 211 */ 212 private void onNext() { 213 mNextButton.setEnabled(false); // Protect against double-tap. 214 215 // Update account object from UI 216 Account account = SetupData.getAccount(); 217 String description = mDescription.getText().toString().trim(); 218 if (!TextUtils.isEmpty(description)) { 219 account.setDisplayName(description); 220 } 221 account.setSenderName(mName.getText().toString().trim()); 222 223 // Launch async task for final commit work 224 // Sicne it's a write task, use the serial executor so even if we ran the task twice 225 // with different values the result would be consistent. 226 new FinalSetupTask(account).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 227 } 228 229 /** 230 * Final account setup work is handled in this AsyncTask: 231 * Commit final values to provider 232 * Trigger account backup 233 * Check for security hold 234 * 235 * When this completes, we return to UI thread for the following steps: 236 * If security hold, dispatch to AccountSecurity activity 237 * Otherwise, return to AccountSetupBasics for conclusion. 238 * 239 * TODO: If there was *any* indication that security might be required, we could at least 240 * force the DeviceAdmin activation step, without waiting for the initial sync/handshake 241 * to fail. 242 * TODO: If the user doesn't update the security, don't go to the MessageList. 243 */ 244 private class FinalSetupTask extends AsyncTask<Void, Void, Boolean> { 245 246 private final Account mAccount; 247 private final Context mContext; 248 249 public FinalSetupTask(Account account) { 250 mAccount = account; 251 mContext = AccountSetupNames.this; 252 } 253 254 @Override 255 protected Boolean doInBackground(Void... params) { 256 // Update the account in the database 257 ContentValues cv = new ContentValues(); 258 cv.put(AccountColumns.DISPLAY_NAME, mAccount.getDisplayName()); 259 cv.put(AccountColumns.SENDER_NAME, mAccount.getSenderName()); 260 mAccount.update(mContext, cv); 261 262 // Update the backup (side copy) of the accounts 263 AccountBackupRestore.backup(AccountSetupNames.this); 264 265 return Account.isSecurityHold(mContext, mAccount.mId); 266 } 267 268 @Override 269 protected void onPostExecute(Boolean isSecurityHold) { 270 if (!isCancelled()) { 271 if (isSecurityHold) { 272 Intent i = AccountSecurity.actionUpdateSecurityIntent( 273 AccountSetupNames.this, mAccount.mId, false); 274 startActivityForResult(i, REQUEST_SECURITY); 275 } else { 276 finishActivity(); 277 } 278 } 279 } 280 } 281 282 /** 283 * Handle the eventual result from the security update activity 284 * 285 * TODO: If the user doesn't update the security, don't go to the MessageList. 286 */ 287 @Override 288 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 289 switch (requestCode) { 290 case REQUEST_SECURITY: 291 finishActivity(); 292 } 293 super.onActivityResult(requestCode, resultCode, data); 294 } 295 296} 297