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