Fragmentize Exchange setup

Change-Id: Ib331d9a7f7be7acbfe355d6a03d9ae07af0cc627
This commit is contained in:
Andrew Stadler 2010-08-11 16:07:26 -07:00
parent 41502f6ea8
commit 641ae45358
5 changed files with 556 additions and 348 deletions

View File

@ -4,9 +4,9 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -25,71 +25,13 @@
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
<fragment
android:id="@+id/setup_exchange_fragment"
android:name="com.android.email.activity.setup.AccountSetupExchangeFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:text="@string/account_setup_exchange_username_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<EditText
android:id="@+id/account_username"
android:inputType="textEmailAddress"
android:imeOptions="actionDone"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<TextView
android:text="@string/account_setup_incoming_password_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<EditText
android:id="@+id/account_password"
android:inputType="textPassword"
android:imeOptions="actionDone"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<!-- This text may be changed in code if the server is IMAP, etc. -->
<TextView
android:text="@string/account_setup_exchange_server_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<!-- Note: we use inputType=textUri as the closest approximation to a server name -->
<EditText
android:id="@+id/account_server"
android:inputType="textUri"
android:imeOptions="actionDone"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<CheckBox
android:id="@+id/account_ssl"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/account_setup_exchange_ssl_label" />
<CheckBox
android:id="@+id/account_trust_certificates"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/account_setup_exchange_trust_certificates_label" />
<TextView
android:text="@string/account_setup_exchange_device_id_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/device_id"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#ffbebebe" />
</LinearLayout>
/>
<RelativeLayout
android:layout_width="match_parent"

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:text="@string/account_setup_exchange_username_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<EditText
android:id="@+id/account_username"
android:inputType="textEmailAddress"
android:imeOptions="actionDone"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<TextView
android:text="@string/account_setup_incoming_password_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<EditText
android:id="@+id/account_password"
android:inputType="textPassword"
android:imeOptions="actionDone"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<!-- This text may be changed in code if the server is IMAP, etc. -->
<TextView
android:text="@string/account_setup_exchange_server_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<!-- Note: we use inputType=textUri as the closest approximation to a server name -->
<EditText
android:id="@+id/account_server"
android:inputType="textUri"
android:imeOptions="actionDone"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<CheckBox
android:id="@+id/account_ssl"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/account_setup_exchange_ssl_label" />
<CheckBox
android:id="@+id/account_trust_certificates"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/account_setup_exchange_trust_certificates_label" />
<TextView
android:text="@string/account_setup_exchange_device_id_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/device_id"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#ffbebebe" />
</LinearLayout>

View File

@ -16,38 +16,20 @@
package com.android.email.activity.setup;
import com.android.email.AccountBackupRestore;
import com.android.email.ExchangeUtils;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.SecurityPolicy.PolicySet;
import com.android.email.Utility;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.HostAuth;
import com.android.email.service.EmailServiceProxy;
import com.android.exchange.SyncManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Provides generic setup for Exchange accounts. The following fields are supported:
@ -83,23 +65,16 @@ import java.net.URISyntaxException;
* Proceed to options screen
* finish() (removes self from back stack)
*
* NOTE: The manifest for this activity has it ignore config changes, because
* TODO: The manifest for this activity has it ignore config changes, because
* we don't want to restart on every orientation - this would launch autodiscover again.
* Do not attempt to define orientation-specific resources, they won't be loaded.
* What we really need here is a more "sticky" way to prevent that problem.
*/
public class AccountSetupExchange extends AccountSetupActivity implements OnClickListener,
OnCheckedChangeListener {
private final static int DIALOG_DUPLICATE_ACCOUNT = 1;
private EditText mUsernameView;
private EditText mPasswordView;
private EditText mServerView;
private CheckBox mSslSecurityView;
private CheckBox mTrustCertificatesView;
public class AccountSetupExchange extends AccountSetupActivity
implements OnClickListener, AccountSetupExchangeFragment.Callback {
/* package */ AccountSetupExchangeFragment mFragment;
private Button mNextButton;
private String mCacheLoginCredential;
private String mDuplicateAccountName;
public static void actionIncomingSettings(Activity fromActivity, int mode, Account acct) {
SetupData.init(mode, acct);
@ -123,165 +98,38 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_exchange);
mUsernameView = (EditText) findViewById(R.id.account_username);
mPasswordView = (EditText) findViewById(R.id.account_password);
mServerView = (EditText) findViewById(R.id.account_server);
mSslSecurityView = (CheckBox) findViewById(R.id.account_ssl);
mSslSecurityView.setOnCheckedChangeListener(this);
mTrustCertificatesView = (CheckBox) findViewById(R.id.account_trust_certificates);
mFragment = (AccountSetupExchangeFragment) findFragmentById(R.id.setup_exchange_fragment);
mNextButton = (Button)findViewById(R.id.next);
mNextButton.setOnClickListener(this);
/*
* Calls validateFields() which enables or disables the Next button
* based on the fields' validity.
*/
TextWatcher validationTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
validateFields();
}
mFragment.setCallback(this);
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
startAutoDiscover();
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
mUsernameView.addTextChangedListener(validationTextWatcher);
mPasswordView.addTextChangedListener(validationTextWatcher);
mServerView.addTextChangedListener(validationTextWatcher);
/**
* If the conditions are right, launch the autodiscover activity. If it succeeds (even
* partially) it will prefill the setup fields and we can proceed as if the user entered them.
*
* Conditions for skipping:
* Editing existing account
* AutoDiscover blocked (used for unit testing only)
* Username or password not entered yet
*/
private void startAutoDiscover() {
if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT
|| !SetupData.isAllowAutodiscover()) {
return;
}
Account account = SetupData.getAccount();
loadFields(account);
validateFields();
// If we've got a username and password and we're NOT editing, try autodiscover
String username = account.mHostAuthRecv.mLogin;
String password = account.mHostAuthRecv.mPassword;
Intent intent = getIntent();
if (username != null && password != null &&
SetupData.getFlowMode() != SetupData.FLOW_MODE_EDIT) {
// NOTE: Disabling AutoDiscover is only used in unit tests
if (SetupData.isAllowAutodiscover()) {
AccountSetupCheckSettings.actionAutoDiscover(this, account.mEmailAddress, password);
}
if (username != null && password != null) {
AccountSetupCheckSettings.actionAutoDiscover(this, account.mEmailAddress, password);
}
//EXCHANGE-REMOVE-SECTION-START
// Show device ID
try {
((TextView) findViewById(R.id.device_id)).setText(SyncManager.getDeviceId(this));
} catch (IOException ignore) {
// There's nothing we can do here...
}
//EXCHANGE-REMOVE-SECTION-END
}
private boolean usernameFieldValid(EditText usernameView) {
return Utility.isTextViewNotEmpty(usernameView) &&
!usernameView.getText().toString().equals("\\");
}
/**
* Prepare a cached dialog with current values (e.g. account name)
*/
@Override
public Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_DUPLICATE_ACCOUNT:
return new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.account_duplicate_dlg_title)
.setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
mDuplicateAccountName))
.setPositiveButton(R.string.okay_action,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dismissDialog(DIALOG_DUPLICATE_ACCOUNT);
}
})
.create();
}
return null;
}
/**
* Update a cached dialog with current values (e.g. account name)
*/
@Override
public void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DIALOG_DUPLICATE_ACCOUNT:
if (mDuplicateAccountName != null) {
AlertDialog alert = (AlertDialog) dialog;
alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
mDuplicateAccountName));
}
break;
}
}
/**
* Copy mAccount's values into UI fields
*/
/* package */ void loadFields(Account account) {
HostAuth hostAuth = account.mHostAuthRecv;
String userName = hostAuth.mLogin;
if (userName != null) {
// Add a backslash to the start of the username, but only if the username has no
// backslash in it.
if (userName.indexOf('\\') < 0) {
userName = "\\" + userName;
}
mUsernameView.setText(userName);
}
if (hostAuth.mPassword != null) {
mPasswordView.setText(hostAuth.mPassword);
}
String protocol = hostAuth.mProtocol;
if (protocol == null || !protocol.startsWith("eas")) {
throw new Error("Unknown account type: " + account.getStoreUri(this));
}
if (hostAuth.mAddress != null) {
mServerView.setText(hostAuth.mAddress);
}
boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL);
boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES);
mSslSecurityView.setChecked(ssl);
mTrustCertificatesView.setChecked(trustCertificates);
mTrustCertificatesView.setVisibility(ssl ? View.VISIBLE : View.GONE);
}
/**
* Check the values in the fields and decide if it makes sense to enable the "next" button
* NOTE: Does it make sense to extract & combine with similar code in AccountSetupIncoming?
* @return true if all fields are valid, false if fields are incomplete
*/
private boolean validateFields() {
boolean enabled = usernameFieldValid(mUsernameView)
&& Utility.isTextViewNotEmpty(mPasswordView)
&& Utility.isTextViewNotEmpty(mServerView);
if (enabled) {
try {
URI uri = getUri();
} catch (URISyntaxException use) {
enabled = false;
}
}
mNextButton.setEnabled(enabled);
Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128);
return enabled;
}
private void doOptions(PolicySet policySet) {
AccountSetupOptions.actionOptions(this);
finish();
}
/**
@ -306,36 +154,14 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic
}
/**
* Process activity result when validating existing account
* Process activity result when validating existing account. If OK, save and finish;
* otherwise simply remain in activity for further editing.
*/
private void doActivityResultValidateExistingAccount(int resultCode, Intent data) {
Account account = SetupData.getAccount();
if (resultCode == RESULT_OK) {
if (account.isSaved()) {
// Account.update will NOT save the HostAuth's
account.update(this, account.toContentValues());
account.mHostAuthRecv.update(this,
account.mHostAuthRecv.toContentValues());
account.mHostAuthSend.update(this,
account.mHostAuthSend.toContentValues());
if (account.mHostAuthRecv.mProtocol.equals("eas")) {
// For EAS, notify SyncManager that the password has changed
try {
ExchangeUtils.getExchangeEmailService(this, null)
.hostChanged(account.mId);
} catch (RemoteException e) {
// Nothing to be done if this fails
}
}
} else {
// Account.save will save the HostAuth's
account.save(this);
}
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(this);
mFragment.saveSettingsAfterEdit();
finish();
}
// else (resultCode not OK) - just return into this activity for further editing
}
/**
@ -349,7 +175,8 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic
ps = (PolicySet)data.getParcelableExtra(
EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET);
}
doOptions(ps);
AccountSetupOptions.actionOptions(this);
finish();
} else if (resultCode == AccountSetupCheckSettings.RESULT_SECURITY_REQUIRED_USER_CANCEL) {
finish();
}
@ -357,7 +184,7 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic
}
/**
* Process activity result when validating new account
* Process activity result when provisioning new account via autodiscovery
*/
private void doActivityResultAutoDiscoverNewAccount(int resultCode, Intent data) {
// If authentication failed, exit immediately (to re-enter credentials)
@ -371,94 +198,38 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic
Parcelable p = data.getParcelableExtra("HostAuth");
if (p != null) {
HostAuth hostAuth = (HostAuth)p;
Account account = SetupData.getAccount();
account.mHostAuthSend = hostAuth;
account.mHostAuthRecv = hostAuth;
loadFields(account);
if (validateFields()) {
boolean valid = mFragment.setHostAuthFromAutodiscover(hostAuth);
if (valid) {
// "click" next to launch server verification
onNext();
mFragment.onNext();
}
}
}
// Otherwise, proceed into this activity for manual setup
}
/**
* Attempt to create a URI from the fields provided. Throws URISyntaxException if there's
* a problem with the user input.
* @return a URI built from the account setup fields
*/
private URI getUri() throws URISyntaxException {
boolean sslRequired = mSslSecurityView.isChecked();
boolean trustCertificates = mTrustCertificatesView.isChecked();
String scheme = (sslRequired)
? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+")
: "eas";
String userName = mUsernameView.getText().toString().trim();
// Remove a leading backslash, if there is one, since we now automatically put one at
// the start of the username field
if (userName.startsWith("\\")) {
userName = userName.substring(1);
}
mCacheLoginCredential = userName;
String userInfo = userName + ":" + mPasswordView.getText().toString().trim();
String host = mServerView.getText().toString().trim();
String path = null;
URI uri = new URI(
scheme,
userInfo,
host,
0,
path,
null,
null);
return uri;
}
/**
* Note, in EAS, store & sender are the same, so we always populate them together
*/
private void onNext() {
try {
URI uri = getUri();
Account setupAccount = SetupData.getAccount();
setupAccount.setStoreUri(this, uri.toString());
setupAccount.setSenderUri(this, uri.toString());
// Stop here if the login credentials duplicate an existing account
// (unless they duplicate the existing account, as they of course will)
Account account = Utility.findExistingAccount(this, setupAccount.mId,
uri.getHost(), mCacheLoginCredential);
if (account != null) {
mDuplicateAccountName = account.mDisplayName;
this.showDialog(DIALOG_DUPLICATE_ACCOUNT);
return;
}
} catch (URISyntaxException use) {
/*
* It's unrecoverable if we cannot create a URI from components that
* we validated to be safe.
*/
throw new Error(use);
}
AccountSetupCheckSettings.actionCheckSettings(this, SetupData.CHECK_INCOMING);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.next:
onNext();
mFragment.onNext();
break;
}
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.getId() == R.id.account_ssl) {
mTrustCertificatesView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
/**
* Implements AccountSetupIncomingFragment.Listener
*/
public void onProceedNext() {
AccountSetupCheckSettings.actionCheckSettings(this, SetupData.CHECK_INCOMING);
}
/**
* Implements AccountSetupIncomingFragment.Listener
*/
public void onEnableProceedButtons(boolean enabled) {
mNextButton.setEnabled(enabled);
// Dim the next button's icon to 50% if the button is disabled.
// TODO this can probably be done with a stateful drawable. (check android:state_enabled)
Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128);
}
}

View File

@ -0,0 +1,406 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity.setup;
import com.android.email.AccountBackupRestore;
import com.android.email.Email;
import com.android.email.ExchangeUtils;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.HostAuth;
import com.android.exchange.SyncManager;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.TextView;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Provides generic setup for Exchange accounts.
*
* TODO: The manifest for this activity has it ignore config changes, because
* we don't want to restart on every orientation - this would launch autodiscover again.
* Do not attempt to define orientation-specific resources, they won't be loaded.
* What we really need here is a more "sticky" way to prevent that problem.
*/
public class AccountSetupExchangeFragment extends Fragment implements OnCheckedChangeListener {
private final static String STATE_KEY_CREDENTIAL =
"AccountSetupExchangeFragment.loginCredential";
private EditText mUsernameView;
private EditText mPasswordView;
private EditText mServerView;
private CheckBox mSslSecurityView;
private CheckBox mTrustCertificatesView;
// Support for lifecycle
private Context mContext;
private Callback mCallback = EmptyCallback.INSTANCE;
private boolean mStarted;
private boolean mLoaded;
private String mCacheLoginCredential;
/**
* Callback interface that owning activities must implement
*/
public interface Callback {
public void onEnableProceedButtons(boolean enable);
public void onProceedNext();
}
private static class EmptyCallback implements Callback {
public static final Callback INSTANCE = new EmptyCallback();
@Override public void onProceedNext() { }
@Override public void onEnableProceedButtons(boolean enable) { }
}
/**
* Called to do initial creation of a fragment. This is called after
* {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreate");
}
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreateView");
}
View view = inflater.inflate(R.layout.account_setup_exchange_fragment, container, false);
Context context = getActivity();
mUsernameView = (EditText) view.findViewById(R.id.account_username);
mPasswordView = (EditText) view.findViewById(R.id.account_password);
mServerView = (EditText) view.findViewById(R.id.account_server);
mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl);
mSslSecurityView.setOnCheckedChangeListener(this);
mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates);
// Calls validateFields() which enables or disables the Next button
// based on the fields' validity.
TextWatcher validationTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
validateFields();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
public void onTextChanged(CharSequence s, int start, int before, int count) { }
};
mUsernameView.addTextChangedListener(validationTextWatcher);
mPasswordView.addTextChangedListener(validationTextWatcher);
mServerView.addTextChangedListener(validationTextWatcher);
//EXCHANGE-REMOVE-SECTION-START
// Show device ID
try {
String deviceId = SyncManager.getDeviceId(context);
((TextView) view.findViewById(R.id.device_id)).setText(deviceId);
} catch (IOException ignore) {
// There's nothing we can do here...
}
//EXCHANGE-REMOVE-SECTION-END
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated");
}
super.onActivityCreated(savedInstanceState);
}
/**
* Called when the Fragment is visible to the user.
*/
@Override
public void onStart() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStart");
}
super.onStart();
mStarted = true;
if (!mLoaded) {
loadSettings(SetupData.getAccount());
}
}
/**
* Called when the fragment is visible to the user and actively running.
*/
@Override
public void onResume() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onResume");
}
super.onResume();
validateFields();
}
@Override
public void onPause() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onPause");
}
super.onPause();
}
/**
* Called when the Fragment is no longer started.
*/
@Override
public void onStop() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStop");
}
super.onStop();
mStarted = false;
}
/**
* Called when the fragment is no longer in use.
*/
@Override
public void onDestroy() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onDestroy");
}
super.onDestroy();
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState");
}
super.onSaveInstanceState(outState);
outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential);
}
/**
* Activity provides callbacks here. This also triggers loading and setting up the UX
*/
public void setCallback(Callback callback) {
mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
mContext = getActivity();
if (mStarted && !mLoaded) {
loadSettings(SetupData.getAccount());
}
}
/**
* Load the current settings into the UI
*
* @return true if the loaded values pass validation
*/
/* package */ boolean loadSettings(Account account) {
HostAuth hostAuth = account.mHostAuthRecv;
String userName = hostAuth.mLogin;
if (userName != null) {
// Add a backslash to the start of the username, but only if the username has no
// backslash in it.
if (userName.indexOf('\\') < 0) {
userName = "\\" + userName;
}
mUsernameView.setText(userName);
}
if (hostAuth.mPassword != null) {
mPasswordView.setText(hostAuth.mPassword);
}
String protocol = hostAuth.mProtocol;
if (protocol == null || !protocol.startsWith("eas")) {
throw new Error("Unknown account type: " + account.getStoreUri(mContext));
}
if (hostAuth.mAddress != null) {
mServerView.setText(hostAuth.mAddress);
}
boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL);
boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES);
mSslSecurityView.setChecked(ssl);
mTrustCertificatesView.setChecked(trustCertificates);
mTrustCertificatesView.setVisibility(ssl ? View.VISIBLE : View.GONE);
return validateFields();
}
private boolean usernameFieldValid(EditText usernameView) {
return Utility.isTextViewNotEmpty(usernameView) &&
!usernameView.getText().toString().equals("\\");
}
/**
* Check the values in the fields and decide if it makes sense to enable the "next" button
* @return true if all fields are valid, false if any fields are incomplete
*/
private boolean validateFields() {
boolean enabled = usernameFieldValid(mUsernameView)
&& Utility.isTextViewNotEmpty(mPasswordView)
&& Utility.isTextViewNotEmpty(mServerView);
if (enabled) {
try {
URI uri = getUri();
} catch (URISyntaxException use) {
enabled = false;
}
}
mCallback.onEnableProceedButtons(enabled);
return enabled;
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.getId() == R.id.account_ssl) {
mTrustCertificatesView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
}
/**
* Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT.
*
* TODO: Was the !isSaved() logic ever actually used?
*/
public void saveSettingsAfterEdit() {
Account account = SetupData.getAccount();
if (account.isSaved()) {
// Account.update will NOT save the HostAuth's
account.update(mContext, account.toContentValues());
account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
if (account.mHostAuthRecv.mProtocol.equals("eas")) {
// For EAS, notify SyncManager that the password has changed
try {
ExchangeUtils.getExchangeEmailService(mContext, null).hostChanged(account.mId);
} catch (RemoteException e) {
// Nothing to be done if this fails
}
}
} else {
// Account.save will save the HostAuth's
account.save(mContext);
}
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(mContext);
}
/**
* Entry point from Activity after entering new settings and verifying them. For setup mode.
*/
public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) {
Account account = SetupData.getAccount();
account.mHostAuthSend = newHostAuth;
account.mHostAuthRecv = newHostAuth;
return loadSettings(account);
}
/**
* Attempt to create a URI from the fields provided. Throws URISyntaxException if there's
* a problem with the user input.
* @return a URI built from the account setup fields
*/
private URI getUri() throws URISyntaxException {
boolean sslRequired = mSslSecurityView.isChecked();
boolean trustCertificates = mTrustCertificatesView.isChecked();
String scheme = (sslRequired)
? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+")
: "eas";
String userName = mUsernameView.getText().toString().trim();
// Remove a leading backslash, if there is one, since we now automatically put one at
// the start of the username field
if (userName.startsWith("\\")) {
userName = userName.substring(1);
}
mCacheLoginCredential = userName;
String userInfo = userName + ":" + mPasswordView.getText().toString().trim();
String host = mServerView.getText().toString().trim();
String path = null;
URI uri = new URI(
scheme,
userInfo,
host,
0,
path,
null,
null);
return uri;
}
/**
* Entry point from Activity, when "next" button is clicked
*/
public void onNext() {
try {
URI uri = getUri();
Account setupAccount = SetupData.getAccount();
setupAccount.setStoreUri(mContext, uri.toString());
setupAccount.setSenderUri(mContext, uri.toString());
// Stop here if the login credentials duplicate an existing account
// (unless they duplicate the existing account, as they of course will)
Account account = Utility.findExistingAccount(mContext, setupAccount.mId,
uri.getHost(), mCacheLoginCredential);
if (account != null) {
DuplicateAccountDialogFragment dialogFragment =
new DuplicateAccountDialogFragment(account.mDisplayName, getId());
dialogFragment.show(getActivity(), DuplicateAccountDialogFragment.TAG);
return;
}
} catch (URISyntaxException use) {
/*
* It's unrecoverable if we cannot create a URI from components that
* we validated to be safe.
*/
throw new Error(use);
}
mCallback.onProceedNext();
}
}

View File

@ -128,12 +128,12 @@ public class AccountSetupExchangeTests extends
}
/**
* Test aspects of loadFields()
* Test aspects of loadSettings()
*
* TODO: More cases
*/
@UiThreadTest
public void testLoadFields() {
public void testLoadSettings() {
// The default URI has no SSL and no "trust"
getActivityAndFields();
assertFalse(mSslRequiredCheckbox.isChecked());
@ -148,7 +148,8 @@ public class AccountSetupExchangeTests extends
"eas", "hostauth", 1, false, mActivity.getBaseContext());
account.mHostAuthRecv.mFlags |= HostAuth.FLAG_SSL;
account.mHostAuthRecv.mFlags &= ~HostAuth.FLAG_TRUST_ALL_CERTIFICATES;
mActivity.loadFields(account);
boolean loadResult = mActivity.mFragment.loadSettings(account);
assertTrue(loadResult);
assertTrue(mSslRequiredCheckbox.isChecked());
assertFalse(mTrustAllCertificatesCheckbox.isChecked());
assertTrue(mTrustAllCertificatesCheckbox.getVisibility() == View.VISIBLE);
@ -156,10 +157,16 @@ public class AccountSetupExchangeTests extends
// Setup host auth with variants of SSL enabled and check. This also enables the
// "trust certificates" checkbox (not checked, but visible now).
account.mHostAuthRecv.mFlags |= HostAuth.FLAG_TRUST_ALL_CERTIFICATES;
mActivity.loadFields(account);
loadResult = mActivity.mFragment.loadSettings(account);
assertTrue(loadResult);
assertTrue(mSslRequiredCheckbox.isChecked());
assertTrue(mTrustAllCertificatesCheckbox.isChecked());
assertTrue(mTrustAllCertificatesCheckbox.getVisibility() == View.VISIBLE);
// A simple test of an incomplete account, which will fail validation
account.mHostAuthRecv.mPassword = "";
loadResult = mActivity.mFragment.loadSettings(account);
assertFalse(loadResult);
}
/**