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