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