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.LoaderManager; 21 import android.content.Context; 22 import android.content.Loader; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.view.View; 26 import android.view.View.OnClickListener; 27 import android.view.View.OnFocusChangeListener; 28 import android.view.inputmethod.InputMethodManager; 29 import android.widget.TextView; 30 31 import com.android.email.R; 32 import com.android.email.activity.UiUtilities; 33 import com.android.emailcommon.provider.Account; 34 import com.android.emailcommon.provider.HostAuth; 35 36 /** 37 * Common base class for server settings fragments, so they can be more easily manipulated by 38 * AccountSettingsXL. Provides the following common functionality: 39 * 40 * Activity-provided callbacks 41 * Activity callback during onAttach 42 * Present "Next" button and respond to its clicks 43 */ 44 public abstract class AccountServerBaseFragment extends AccountSetupFragment 45 implements OnClickListener { 46 47 private static final String BUNDLE_KEY_SETTINGS = "AccountServerBaseFragment.settings"; 48 private static final String BUNDLE_KEY_ACTIVITY_TITLE = "AccountServerBaseFragment.title"; 49 private static final String BUNDLE_KEY_SAVING = "AccountServerBaseFragment.saving"; 50 private static final String BUNDLE_KEY_SENDAUTH = "AccountServerBaseFragment.sendAuth"; 51 private static final String BUNDLE_KEY_RECVAUTH = "AccountServerBaseFragment.recvAuth"; 52 53 protected Context mAppContext; 54 /** 55 * Whether or not we are in "settings mode". We re-use the same screens for both the initial 56 * account creation as well as subsequent account modification. If this is 57 * <code>false</code>, we are in account creation mode. Otherwise, we are in account 58 * modification mode. 59 */ 60 protected boolean mSettingsMode; 61 protected HostAuth mLoadedSendAuth; 62 protected HostAuth mLoadedRecvAuth; 63 64 protected SetupDataFragment mSetupData; 65 66 // This is null in the setup wizard screens, and non-null in AccountSettings mode 67 private View mProceedButton; 68 protected String mBaseScheme = "protocol"; 69 70 // Set to true if we're in the process of saving 71 private boolean mSaving; 72 73 /** 74 // Used to post the callback once we're done saving, since we can't perform fragment 75 // transactions from {@link LoaderManager.LoaderCallbacks#onLoadFinished(Loader, Object)} 76 */ 77 private Handler mHandler = new Handler(); 78 79 /** 80 * Callback interface that owning activities must provide 81 */ 82 public interface Callback extends AccountSetupFragment.Callback { 83 /** 84 * Called when user clicks "next". Starts account checker. 85 * @param checkMode values from {@link SetupDataFragment} 86 */ 87 public void onAccountServerUIComplete(int checkMode); 88 public void onAccountServerSaveComplete(); 89 } 90 91 /** 92 * Creates and returns a bundle of arguments in the format we expect 93 * 94 * @param settingsMode True if we're in settings, false if we're in account creation 95 * @return Arg bundle 96 */ 97 public static Bundle getArgs(boolean settingsMode) { 98 final Bundle setupModeArgs = new Bundle(1); 99 setupModeArgs.putBoolean(BUNDLE_KEY_SETTINGS, settingsMode); 100 return setupModeArgs; 101 } 102 103 public AccountServerBaseFragment() {} 104 105 /** 106 * At onCreate time, read the fragment arguments 107 */ 108 @Override 109 public void onCreate(Bundle savedInstanceState) { 110 super.onCreate(savedInstanceState); 111 112 // Get arguments, which modally switch us into "settings" mode (different appearance) 113 mSettingsMode = false; 114 if (savedInstanceState != null) { 115 mSettingsMode = savedInstanceState.getBoolean(BUNDLE_KEY_SETTINGS); 116 mSaving = savedInstanceState.getBoolean(BUNDLE_KEY_SAVING); 117 mLoadedSendAuth = savedInstanceState.getParcelable(BUNDLE_KEY_SENDAUTH); 118 mLoadedRecvAuth = savedInstanceState.getParcelable(BUNDLE_KEY_RECVAUTH); 119 } else if (getArguments() != null) { 120 mSettingsMode = getArguments().getBoolean(BUNDLE_KEY_SETTINGS); 121 } 122 setHasOptionsMenu(true); 123 } 124 125 /** 126 * Called from onCreateView, to do settings mode configuration 127 */ 128 protected void onCreateViewSettingsMode(View view) { 129 if (mSettingsMode) { 130 UiUtilities.getView(view, R.id.cancel).setOnClickListener(this); 131 mProceedButton = UiUtilities.getView(view, R.id.done); 132 mProceedButton.setOnClickListener(this); 133 mProceedButton.setEnabled(false); 134 } 135 } 136 137 @Override 138 public void onActivityCreated(Bundle savedInstanceState) { 139 final Activity activity = getActivity(); 140 mAppContext = activity.getApplicationContext(); 141 if (mSettingsMode && savedInstanceState != null) { 142 // startPreferencePanel launches this fragment with the right title initially, but 143 // if the device is rotated we must set the title ourselves 144 activity.setTitle(savedInstanceState.getString(BUNDLE_KEY_ACTIVITY_TITLE)); 145 } 146 SetupDataFragment.SetupDataContainer container = 147 (SetupDataFragment.SetupDataContainer) activity; 148 mSetupData = container.getSetupData(); 149 150 super.onActivityCreated(savedInstanceState); 151 } 152 153 @Override 154 public void onResume() { 155 super.onResume(); 156 if (mSaving) { 157 // We need to call this here in case the save completed while we weren't resumed 158 saveSettings(); 159 } 160 } 161 162 @Override 163 public void onSaveInstanceState(Bundle outState) { 164 super.onSaveInstanceState(outState); 165 outState.putString(BUNDLE_KEY_ACTIVITY_TITLE, (String) getActivity().getTitle()); 166 outState.putBoolean(BUNDLE_KEY_SETTINGS, mSettingsMode); 167 outState.putParcelable(BUNDLE_KEY_SENDAUTH, mLoadedSendAuth); 168 outState.putParcelable(BUNDLE_KEY_RECVAUTH, mLoadedRecvAuth); 169 } 170 171 @Override 172 public void onPause() { 173 // Hide the soft keyboard if we lose focus 174 final InputMethodManager imm = 175 (InputMethodManager) mAppContext.getSystemService(Context.INPUT_METHOD_SERVICE); 176 imm.hideSoftInputFromWindow(getView().getWindowToken(), 0); 177 super.onPause(); 178 } 179 180 /** 181 * Implements OnClickListener 182 */ 183 @Override 184 public void onClick(View v) { 185 final int viewId = v.getId(); 186 if (viewId == R.id.cancel) { 187 collectUserInputInternal(); 188 getActivity().onBackPressed(); 189 } else if (viewId == R.id.done) { 190 collectUserInput(); 191 } else { 192 super.onClick(v); 193 } 194 } 195 196 /** 197 * Enable/disable the "next" button 198 */ 199 public void enableNextButton(boolean enable) { 200 // If we are in settings "mode" we may be showing our own next button, and we'll 201 // enable it directly, here 202 if (mProceedButton != null) { 203 mProceedButton.setEnabled(enable); 204 } else { 205 setNextButtonEnabled(enable); 206 } 207 } 208 209 /** 210 * Make the given text view uneditable. If the text view is ever focused, the specified 211 * error message will be displayed. 212 */ 213 protected void makeTextViewUneditable(final TextView view, final String errorMessage) { 214 // We're editing an existing account; don't allow modification of the user name 215 if (mSettingsMode) { 216 view.setKeyListener(null); 217 view.setFocusable(true); 218 view.setOnFocusChangeListener(new OnFocusChangeListener() { 219 @Override 220 public void onFocusChange(View v, boolean hasFocus) { 221 if (hasFocus) { 222 // Framework will not auto-hide IME; do it ourselves 223 InputMethodManager imm = (InputMethodManager) mAppContext. 224 getSystemService(Context.INPUT_METHOD_SERVICE); 225 imm.hideSoftInputFromWindow(getView().getWindowToken(), 0); 226 view.setError(errorMessage); 227 } else { 228 view.setError(null); 229 } 230 } 231 }); 232 view.setOnClickListener(new OnClickListener() { 233 @Override 234 public void onClick(View v) { 235 if (view.getError() == null) { 236 view.setError(errorMessage); 237 } else { 238 view.setError(null); 239 } 240 } 241 }); 242 } 243 } 244 245 /** 246 * Returns whether or not any settings have changed. 247 */ 248 public boolean haveSettingsChanged() { 249 collectUserInputInternal(); 250 final Account account = mSetupData.getAccount(); 251 252 final HostAuth sendAuth = account.getOrCreateHostAuthSend(mAppContext); 253 final boolean sendChanged = (mLoadedSendAuth != null && !mLoadedSendAuth.equals(sendAuth)); 254 255 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mAppContext); 256 final boolean recvChanged = (mLoadedRecvAuth != null && !mLoadedRecvAuth.equals(recvAuth)); 257 258 return sendChanged || recvChanged; 259 } 260 261 public void saveSettings() { 262 getLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Boolean>() { 263 @Override 264 public Loader<Boolean> onCreateLoader(int id, Bundle args) { 265 return getSaveSettingsLoader(); 266 } 267 268 @Override 269 public void onLoadFinished(Loader<Boolean> loader, Boolean data) { 270 mHandler.post(new Runnable() { 271 @Override 272 public void run() { 273 if (isResumed()) { 274 final Callback callback = (Callback) getActivity(); 275 callback.onAccountServerSaveComplete(); 276 } 277 } 278 }); 279 } 280 281 @Override 282 public void onLoaderReset(Loader<Boolean> loader) {} 283 }); 284 } 285 286 public abstract Loader<Boolean> getSaveSettingsLoader(); 287 288 /** 289 * Collect the user's input into the setup data object. Concrete classes must implement. 290 */ 291 public abstract int collectUserInputInternal(); 292 293 public void collectUserInput() { 294 final int phase = collectUserInputInternal(); 295 final Callback callback = (Callback) getActivity(); 296 callback.onAccountServerUIComplete(phase); 297 } 298 } 299