AccountServerBaseFragment.java revision bc2eaadde987044027b57d241e635de014bdb8ba
1/* 2 * Copyright (C) 2010 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 com.android.email.R; 20import com.android.email.activity.UiUtilities; 21import com.android.emailcommon.provider.Account; 22import com.android.emailcommon.provider.HostAuth; 23import com.android.emailcommon.utility.Utility; 24 25import android.app.Activity; 26import android.app.Fragment; 27import android.content.Context; 28import android.os.AsyncTask; 29import android.os.Bundle; 30import android.view.View; 31import android.view.View.OnClickListener; 32import android.view.View.OnFocusChangeListener; 33import android.view.inputmethod.InputMethodManager; 34import android.widget.Button; 35import android.widget.TextView; 36 37import java.net.URI; 38import java.net.URISyntaxException; 39 40/** 41 * Common base class for server settings fragments, so they can be more easily manipulated by 42 * AccountSettingsXL. Provides the following common functionality: 43 * 44 * Activity-provided callbacks 45 * Activity callback during onAttach 46 * Present "Next" button and respond to its clicks 47 */ 48public abstract class AccountServerBaseFragment extends Fragment 49 implements AccountCheckSettingsFragment.Callbacks, OnClickListener { 50 51 public static Bundle sSetupModeArgs = null; 52 protected static URI sDefaultUri; 53 54 private final static String BUNDLE_KEY_SETTINGS = "AccountServerBaseFragment.settings"; 55 56 protected Context mContext; 57 protected Callback mCallback = EmptyCallback.INSTANCE; 58 /** 59 * Whether or not we are in "settings mode". We re-use the same screens for both the initial 60 * account creation as well as subsequent account modification. If <code>mSettingsMode</code> 61 * if <code>false</code>, we are in account creation mode. Otherwise, we are in account 62 * modification mode. 63 */ 64 protected boolean mSettingsMode; 65 /*package*/ HostAuth mLoadedSendAuth; 66 /*package*/ HostAuth mLoadedRecvAuth; 67 68 // This is null in the setup wizard screens, and non-null in AccountSettings mode 69 private Button mProceedButton; 70 // This is used to debounce multiple clicks on the proceed button (which does async work) 71 private boolean mProceedButtonPressed; 72 /*package*/ String mBaseScheme = "protocol"; 73 74 /** 75 * Callback interface that owning activities must provide 76 */ 77 public interface Callback { 78 /** 79 * Called each time the user-entered input transitions between valid and invalid 80 * @param enable true to enable proceed/next button, false to disable 81 */ 82 public void onEnableProceedButtons(boolean enable); 83 84 /** 85 * Called when user clicks "next". Starts account checker. 86 * @param checkMode values from {@link SetupData} 87 * @param target the fragment that requested the check 88 */ 89 public void onProceedNext(int checkMode, AccountServerBaseFragment target); 90 91 /** 92 * Called when account checker completes. Fragments are responsible for saving 93 * own edited data; This is primarily for the activity to do post-check navigation. 94 * @param result check settings result code - success is CHECK_SETTINGS_OK 95 * @param setupMode signals if we were editing or creating 96 */ 97 public void onCheckSettingsComplete(int result, int setupMode); 98 } 99 100 private static class EmptyCallback implements Callback { 101 public static final Callback INSTANCE = new EmptyCallback(); 102 @Override public void onEnableProceedButtons(boolean enable) { } 103 @Override public void onProceedNext(int checkMode, AccountServerBaseFragment target) { } 104 @Override public void onCheckSettingsComplete(int result, int setupMode) { } 105 } 106 107 /** 108 * Get the static arguments bundle that forces a server settings fragment into "settings" mode 109 * (If not included, you'll be in "setup" mode which behaves slightly differently.) 110 */ 111 public static synchronized Bundle getSettingsModeArgs() { 112 if (sSetupModeArgs == null) { 113 sSetupModeArgs = new Bundle(); 114 sSetupModeArgs.putBoolean(BUNDLE_KEY_SETTINGS, true); 115 } 116 return sSetupModeArgs; 117 } 118 119 public AccountServerBaseFragment() { 120 if (sDefaultUri == null) { 121 try { 122 sDefaultUri = new URI(""); 123 } catch (URISyntaxException ignore) { 124 // ignore; will never happen 125 } 126 } 127 } 128 129 /** 130 * At onCreate time, read the fragment arguments 131 */ 132 @Override 133 public void onCreate(Bundle savedInstanceState) { 134 super.onCreate(savedInstanceState); 135 136 // Get arguments, which modally switch us into "settings" mode (different appearance) 137 mSettingsMode = false; 138 if (getArguments() != null) { 139 mSettingsMode = getArguments().getBoolean(BUNDLE_KEY_SETTINGS); 140 } 141 } 142 143 /** 144 * Called from onCreateView, to do settings mode configuration 145 */ 146 protected void onCreateViewSettingsMode(View view) { 147 if (mSettingsMode) { 148 UiUtilities.getView(view, R.id.cancel).setOnClickListener(this); 149 mProceedButton = (Button) UiUtilities.getView(view, R.id.done); 150 mProceedButton.setOnClickListener(this); 151 mProceedButton.setEnabled(false); 152 } 153 } 154 155 @Override 156 public void onAttach(Activity activity) { 157 super.onAttach(activity); 158 mContext = activity; 159 } 160 161 @Override 162 public void onPause() { 163 // Hide the soft keyboard if we lose focus 164 InputMethodManager imm = 165 (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 166 imm.hideSoftInputFromWindow(getView().getWindowToken(), 0); 167 super.onPause(); 168 } 169 170 /** 171 * Implements OnClickListener 172 */ 173 @Override 174 public void onClick(View v) { 175 switch (v.getId()) { 176 case R.id.cancel: 177 getActivity().onBackPressed(); 178 break; 179 case R.id.done: 180 // Simple debounce - just ignore while checks are underway 181 if (mProceedButtonPressed) { 182 return; 183 } 184 mProceedButtonPressed = true; 185 onNext(); 186 break; 187 } 188 } 189 190 /** 191 * Activity provides callbacks here. 192 */ 193 public void setCallback(Callback callback) { 194 mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; 195 mContext = getActivity(); 196 } 197 198 /** 199 * Enable/disable the "next" button 200 */ 201 public void enableNextButton(boolean enable) { 202 // If we are in settings "mode" we may be showing our own next button, and we'll 203 // enable it directly, here 204 if (mProceedButton != null) { 205 mProceedButton.setEnabled(enable); 206 } 207 clearButtonBounce(); 208 209 // TODO: This supports the phone UX activities and will be removed 210 mCallback.onEnableProceedButtons(enable); 211 } 212 213 /** 214 * Performs async operations as part of saving changes to the settings. 215 * Check for duplicate account 216 * Display dialog if necessary 217 * Else, proceed via mCallback.onProceedNext 218 */ 219 protected void startDuplicateTaskCheck(long accountId, String checkHost, String checkLogin, 220 int checkSettingsMode) { 221 new DuplicateCheckTask(accountId, checkHost, checkLogin, checkSettingsMode) 222 .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 223 } 224 225 /** 226 * Make the given text view uneditable. If the text view is ever focused, the specified 227 * error message will be displayed. 228 */ 229 protected void makeTextViewUneditable(final TextView view, final String errorMessage) { 230 // We're editing an existing account; don't allow modification of the user name 231 if (mSettingsMode) { 232 view.setKeyListener(null); 233 view.setFocusable(true); 234 view.setOnFocusChangeListener(new OnFocusChangeListener() { 235 @Override 236 public void onFocusChange(View v, boolean hasFocus) { 237 if (hasFocus) { 238 // Framework will not auto-hide IME; do it ourselves 239 InputMethodManager imm = (InputMethodManager)mContext. 240 getSystemService(Context.INPUT_METHOD_SERVICE); 241 imm.hideSoftInputFromWindow(getView().getWindowToken(), 0); 242 view.setError(errorMessage); 243 } else { 244 view.setError(null); 245 } 246 } 247 }); 248 view.setOnClickListener(new OnClickListener() { 249 @Override 250 public void onClick(View v) { 251 if (view.getError() == null) { 252 view.setError(errorMessage); 253 } else { 254 view.setError(null); 255 } 256 } 257 }); 258 } 259 } 260 261 /** 262 * Clears the "next" button de-bounce flags and allows the "next" button to activate. 263 */ 264 private void clearButtonBounce() { 265 mProceedButtonPressed = false; 266 } 267 268 private class DuplicateCheckTask extends AsyncTask<Void, Void, Account> { 269 270 private final long mAccountId; 271 private final String mCheckHost; 272 private final String mCheckLogin; 273 private final int mCheckSettingsMode; 274 275 public DuplicateCheckTask(long accountId, String checkHost, String checkLogin, 276 int checkSettingsMode) { 277 mAccountId = accountId; 278 mCheckHost = checkHost; 279 mCheckLogin = checkLogin; 280 mCheckSettingsMode = checkSettingsMode; 281 } 282 283 @Override 284 protected Account doInBackground(Void... params) { 285 Account account = Utility.findExistingAccount(mContext, mAccountId, 286 mCheckHost, mCheckLogin); 287 return account; 288 } 289 290 @Override 291 protected void onPostExecute(Account duplicateAccount) { 292 AccountServerBaseFragment fragment = AccountServerBaseFragment.this; 293 if (duplicateAccount != null) { 294 // Show duplicate account warning 295 DuplicateAccountDialogFragment dialogFragment = 296 DuplicateAccountDialogFragment.newInstance(duplicateAccount.mDisplayName); 297 dialogFragment.show(fragment.getFragmentManager(), 298 DuplicateAccountDialogFragment.TAG); 299 } else { 300 // Otherwise, proceed with the save/check 301 mCallback.onProceedNext(mCheckSettingsMode, fragment); 302 } 303 clearButtonBounce(); 304 } 305 } 306 307 /** 308 * Implements AccountCheckSettingsFragment.Callbacks 309 * 310 * Handle OK or error result from check settings. Save settings (async), and then 311 * exit to previous fragment. 312 */ 313 @Override 314 public void onCheckSettingsComplete(final int settingsResult) { 315 new AsyncTask<Void, Void, Void>() { 316 @Override 317 protected Void doInBackground(Void... params) { 318 if (settingsResult == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { 319 if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT) { 320 saveSettingsAfterEdit(); 321 } else { 322 saveSettingsAfterSetup(); 323 } 324 } 325 return null; 326 } 327 328 @Override 329 protected void onPostExecute(Void result) { 330 // Signal to owning activity that a settings check completed 331 mCallback.onCheckSettingsComplete(settingsResult, SetupData.getFlowMode()); 332 } 333 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 334 } 335 336 /** 337 * Implements AccountCheckSettingsFragment.Callbacks 338 * This is overridden only by AccountSetupExchange 339 */ 340 @Override 341 public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { 342 throw new IllegalStateException(); 343 } 344 345 /** 346 * Returns whether or not any settings have changed. 347 */ 348 public boolean haveSettingsChanged() { 349 Account account = SetupData.getAccount(); 350 351 HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 352 boolean sendChanged = (mLoadedSendAuth != null && !mLoadedSendAuth.equals(sendAuth)); 353 354 HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 355 boolean recvChanged = (mLoadedRecvAuth != null && !mLoadedRecvAuth.equals(recvAuth)); 356 357 return sendChanged || recvChanged; 358 } 359 360 /** 361 * Save settings after "OK" result from checker. Concrete classes must implement. 362 * This is called from a worker thread and is allowed to perform DB operations. 363 */ 364 public abstract void saveSettingsAfterEdit(); 365 366 /** 367 * Save settings after "OK" result from checker. Concrete classes must implement. 368 * This is called from a worker thread and is allowed to perform DB operations. 369 */ 370 public abstract void saveSettingsAfterSetup(); 371 372 /** 373 * Respond to a click of the "Next" button. Concrete classes must implement. 374 */ 375 public abstract void onNext(); 376} 377