436 lines
16 KiB
Java
436 lines
16 KiB
Java
/*
|
|
* 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 android.app.Activity;
|
|
import android.app.Fragment;
|
|
import android.content.Context;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.view.KeyEvent;
|
|
import android.view.View;
|
|
import android.view.View.OnClickListener;
|
|
import android.view.View.OnFocusChangeListener;
|
|
import android.view.inputmethod.EditorInfo;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.widget.Button;
|
|
import android.widget.TextView;
|
|
import android.widget.TextView.OnEditorActionListener;
|
|
|
|
import com.android.email.R;
|
|
import com.android.email.activity.UiUtilities;
|
|
import com.android.emailcommon.provider.Account;
|
|
import com.android.emailcommon.provider.HostAuth;
|
|
import com.android.emailcommon.utility.Utility;
|
|
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
|
|
/**
|
|
* Common base class for server settings fragments, so they can be more easily manipulated by
|
|
* AccountSettingsXL. Provides the following common functionality:
|
|
*
|
|
* Activity-provided callbacks
|
|
* Activity callback during onAttach
|
|
* Present "Next" button and respond to its clicks
|
|
*/
|
|
public abstract class AccountServerBaseFragment extends Fragment
|
|
implements AccountCheckSettingsFragment.Callbacks, OnClickListener {
|
|
|
|
public static Bundle sSetupModeArgs = null;
|
|
protected static URI sDefaultUri;
|
|
|
|
private static final String BUNDLE_KEY_SETTINGS = "AccountServerBaseFragment.settings";
|
|
private static final String BUNDLE_KEY_ACTIVITY_TITLE = "AccountServerBaseFragment.title";
|
|
|
|
protected Context mContext;
|
|
protected Callback mCallback = EmptyCallback.INSTANCE;
|
|
/**
|
|
* Whether or not we are in "settings mode". We re-use the same screens for both the initial
|
|
* account creation as well as subsequent account modification. If <code>mSettingsMode</code>
|
|
* if <code>false</code>, we are in account creation mode. Otherwise, we are in account
|
|
* modification mode.
|
|
*/
|
|
protected boolean mSettingsMode;
|
|
/*package*/ HostAuth mLoadedSendAuth;
|
|
/*package*/ HostAuth mLoadedRecvAuth;
|
|
|
|
// This is null in the setup wizard screens, and non-null in AccountSettings mode
|
|
private Button mProceedButton;
|
|
// This is used to debounce multiple clicks on the proceed button (which does async work)
|
|
private boolean mProceedButtonPressed;
|
|
/*package*/ String mBaseScheme = "protocol";
|
|
|
|
/**
|
|
* Callback interface that owning activities must provide
|
|
*/
|
|
public interface Callback {
|
|
/**
|
|
* Called each time the user-entered input transitions between valid and invalid
|
|
* @param enable true to enable proceed/next button, false to disable
|
|
*/
|
|
public void onEnableProceedButtons(boolean enable);
|
|
|
|
/**
|
|
* Called when user clicks "next". Starts account checker.
|
|
* @param checkMode values from {@link SetupData}
|
|
* @param target the fragment that requested the check
|
|
*/
|
|
public void onProceedNext(int checkMode, AccountServerBaseFragment target);
|
|
|
|
/**
|
|
* Called when account checker completes. Fragments are responsible for saving
|
|
* own edited data; This is primarily for the activity to do post-check navigation.
|
|
* @param result check settings result code - success is CHECK_SETTINGS_OK
|
|
* @param setupMode signals if we were editing or creating
|
|
*/
|
|
public void onCheckSettingsComplete(int result, int setupMode);
|
|
}
|
|
|
|
private static class EmptyCallback implements Callback {
|
|
public static final Callback INSTANCE = new EmptyCallback();
|
|
@Override public void onEnableProceedButtons(boolean enable) { }
|
|
@Override public void onProceedNext(int checkMode, AccountServerBaseFragment target) { }
|
|
@Override public void onCheckSettingsComplete(int result, int setupMode) { }
|
|
}
|
|
|
|
/**
|
|
* Get the static arguments bundle that forces a server settings fragment into "settings" mode
|
|
* (If not included, you'll be in "setup" mode which behaves slightly differently.)
|
|
*/
|
|
public static synchronized Bundle getSettingsModeArgs() {
|
|
if (sSetupModeArgs == null) {
|
|
sSetupModeArgs = new Bundle();
|
|
sSetupModeArgs.putBoolean(BUNDLE_KEY_SETTINGS, true);
|
|
}
|
|
return sSetupModeArgs;
|
|
}
|
|
|
|
public AccountServerBaseFragment() {
|
|
if (sDefaultUri == null) {
|
|
try {
|
|
sDefaultUri = new URI("");
|
|
} catch (URISyntaxException ignore) {
|
|
// ignore; will never happen
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* At onCreate time, read the fragment arguments
|
|
*/
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
// Get arguments, which modally switch us into "settings" mode (different appearance)
|
|
mSettingsMode = false;
|
|
if (getArguments() != null) {
|
|
mSettingsMode = getArguments().getBoolean(BUNDLE_KEY_SETTINGS);
|
|
}
|
|
setHasOptionsMenu(true);
|
|
}
|
|
|
|
/**
|
|
* Called from onCreateView, to do settings mode configuration
|
|
*/
|
|
protected void onCreateViewSettingsMode(View view) {
|
|
if (mSettingsMode) {
|
|
UiUtilities.getView(view, R.id.cancel).setOnClickListener(this);
|
|
mProceedButton = (Button) UiUtilities.getView(view, R.id.done);
|
|
mProceedButton.setOnClickListener(this);
|
|
mProceedButton.setEnabled(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
// startPreferencePanel launches this fragment with the right title initially, but
|
|
// if the device is rotate we must set the title ourselves
|
|
if (mSettingsMode && savedInstanceState != null) {
|
|
getActivity().setTitle(savedInstanceState.getString(BUNDLE_KEY_ACTIVITY_TITLE));
|
|
}
|
|
super.onActivityCreated(savedInstanceState);
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState(Bundle outState) {
|
|
outState.putString(BUNDLE_KEY_ACTIVITY_TITLE, (String) getActivity().getTitle());
|
|
}
|
|
|
|
@Override
|
|
public void onAttach(Activity activity) {
|
|
super.onAttach(activity);
|
|
mContext = activity;
|
|
}
|
|
|
|
@Override
|
|
public void onDetach() {
|
|
super.onDetach();
|
|
|
|
// Ensure that we don't have any callbacks at this point.
|
|
mCallback = EmptyCallback.INSTANCE;
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
// Hide the soft keyboard if we lose focus
|
|
InputMethodManager imm =
|
|
(InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
|
|
super.onPause();
|
|
}
|
|
|
|
/**
|
|
* Implements OnClickListener
|
|
*/
|
|
@Override
|
|
public void onClick(View v) {
|
|
switch (v.getId()) {
|
|
case R.id.cancel:
|
|
getActivity().onBackPressed();
|
|
break;
|
|
case R.id.done:
|
|
// Simple debounce - just ignore while checks are underway
|
|
if (mProceedButtonPressed) {
|
|
return;
|
|
}
|
|
mProceedButtonPressed = true;
|
|
onNext();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Activity provides callbacks here.
|
|
*/
|
|
public void setCallback(Callback callback) {
|
|
mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
|
|
mContext = getActivity();
|
|
}
|
|
|
|
/**
|
|
* Enable/disable the "next" button
|
|
*/
|
|
public void enableNextButton(boolean enable) {
|
|
// If we are in settings "mode" we may be showing our own next button, and we'll
|
|
// enable it directly, here
|
|
if (mProceedButton != null) {
|
|
mProceedButton.setEnabled(enable);
|
|
}
|
|
clearButtonBounce();
|
|
|
|
// TODO: This supports the phone UX activities and will be removed
|
|
mCallback.onEnableProceedButtons(enable);
|
|
}
|
|
|
|
/**
|
|
* Performs async operations as part of saving changes to the settings.
|
|
* Check for duplicate account
|
|
* Display dialog if necessary
|
|
* Else, proceed via mCallback.onProceedNext
|
|
*/
|
|
protected void startDuplicateTaskCheck(long accountId, String checkHost, String checkLogin,
|
|
int checkSettingsMode) {
|
|
new DuplicateCheckTask(accountId, checkHost, checkLogin, checkSettingsMode)
|
|
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
}
|
|
|
|
/**
|
|
* Make the given text view uneditable. If the text view is ever focused, the specified
|
|
* error message will be displayed.
|
|
*/
|
|
protected void makeTextViewUneditable(final TextView view, final String errorMessage) {
|
|
// We're editing an existing account; don't allow modification of the user name
|
|
if (mSettingsMode) {
|
|
view.setKeyListener(null);
|
|
view.setFocusable(true);
|
|
view.setOnFocusChangeListener(new OnFocusChangeListener() {
|
|
@Override
|
|
public void onFocusChange(View v, boolean hasFocus) {
|
|
if (hasFocus) {
|
|
// Framework will not auto-hide IME; do it ourselves
|
|
InputMethodManager imm = (InputMethodManager)mContext.
|
|
getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
|
|
view.setError(errorMessage);
|
|
} else {
|
|
view.setError(null);
|
|
}
|
|
}
|
|
});
|
|
view.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
if (view.getError() == null) {
|
|
view.setError(errorMessage);
|
|
} else {
|
|
view.setError(null);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A keyboard listener which dismisses the keyboard when "DONE" is pressed, but doesn't muck
|
|
* around with focus. This is useful in settings screens, as we don't want focus to change
|
|
* since some fields throw up errors when they're focused to give the user more info.
|
|
*/
|
|
protected final OnEditorActionListener mDismissImeOnDoneListener =
|
|
new OnEditorActionListener() {
|
|
@Override
|
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
|
// Dismiss soft keyboard but don't modify focus.
|
|
final Context context = getActivity();
|
|
if (context == null) {
|
|
return false;
|
|
}
|
|
InputMethodManager imm = (InputMethodManager) context.getSystemService(
|
|
Context.INPUT_METHOD_SERVICE);
|
|
if (imm != null && imm.isActive()) {
|
|
imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Clears the "next" button de-bounce flags and allows the "next" button to activate.
|
|
*/
|
|
private void clearButtonBounce() {
|
|
mProceedButtonPressed = false;
|
|
}
|
|
|
|
private class DuplicateCheckTask extends AsyncTask<Void, Void, Account> {
|
|
|
|
private final long mAccountId;
|
|
private final String mCheckHost;
|
|
private final String mCheckLogin;
|
|
private final int mCheckSettingsMode;
|
|
|
|
public DuplicateCheckTask(long accountId, String checkHost, String checkLogin,
|
|
int checkSettingsMode) {
|
|
mAccountId = accountId;
|
|
mCheckHost = checkHost;
|
|
mCheckLogin = checkLogin;
|
|
mCheckSettingsMode = checkSettingsMode;
|
|
}
|
|
|
|
@Override
|
|
protected Account doInBackground(Void... params) {
|
|
Account account = Utility.findExistingAccount(mContext, mAccountId,
|
|
mCheckHost, mCheckLogin);
|
|
return account;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Account duplicateAccount) {
|
|
AccountServerBaseFragment fragment = AccountServerBaseFragment.this;
|
|
if (duplicateAccount != null) {
|
|
// Show duplicate account warning
|
|
DuplicateAccountDialogFragment dialogFragment =
|
|
DuplicateAccountDialogFragment.newInstance(duplicateAccount.mDisplayName);
|
|
dialogFragment.show(fragment.getFragmentManager(),
|
|
DuplicateAccountDialogFragment.TAG);
|
|
} else {
|
|
// Otherwise, proceed with the save/check
|
|
mCallback.onProceedNext(mCheckSettingsMode, fragment);
|
|
}
|
|
clearButtonBounce();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements AccountCheckSettingsFragment.Callbacks
|
|
*
|
|
* Handle OK or error result from check settings. Save settings (async), and then
|
|
* exit to previous fragment.
|
|
*/
|
|
@Override
|
|
public void onCheckSettingsComplete(final int settingsResult) {
|
|
new AsyncTask<Void, Void, Void>() {
|
|
@Override
|
|
protected Void doInBackground(Void... params) {
|
|
if (settingsResult == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) {
|
|
if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT) {
|
|
saveSettingsAfterEdit();
|
|
} else {
|
|
saveSettingsAfterSetup();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Void result) {
|
|
// Signal to owning activity that a settings check completed
|
|
mCallback.onCheckSettingsComplete(settingsResult, SetupData.getFlowMode());
|
|
}
|
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
}
|
|
|
|
/**
|
|
* Implements AccountCheckSettingsFragment.Callbacks
|
|
* This is overridden only by AccountSetupExchange
|
|
*/
|
|
@Override
|
|
public void onAutoDiscoverComplete(int result, HostAuth hostAuth) {
|
|
throw new IllegalStateException();
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not any settings have changed.
|
|
*/
|
|
public boolean haveSettingsChanged() {
|
|
Account account = SetupData.getAccount();
|
|
|
|
HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext);
|
|
boolean sendChanged = (mLoadedSendAuth != null && !mLoadedSendAuth.equals(sendAuth));
|
|
|
|
HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
|
|
boolean recvChanged = (mLoadedRecvAuth != null && !mLoadedRecvAuth.equals(recvAuth));
|
|
|
|
return sendChanged || recvChanged;
|
|
}
|
|
|
|
public boolean setHostAuthFromAutodiscover(HostAuth hostAuth) {
|
|
// This is overridden, if necessary
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Save settings after "OK" result from checker. Concrete classes must implement.
|
|
* This is called from a worker thread and is allowed to perform DB operations.
|
|
*/
|
|
public abstract void saveSettingsAfterEdit();
|
|
|
|
/**
|
|
* Save settings after "OK" result from checker. Concrete classes must implement.
|
|
* This is called from a worker thread and is allowed to perform DB operations.
|
|
*/
|
|
public abstract void saveSettingsAfterSetup();
|
|
|
|
/**
|
|
* Respond to a click of the "Next" button. Concrete classes must implement.
|
|
*/
|
|
public abstract void onNext();
|
|
}
|