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          */
onAccountServerUIComplete(int checkMode)87         public void onAccountServerUIComplete(int checkMode);
onAccountServerSaveComplete()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      */
getArgs(boolean settingsMode)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 
AccountServerBaseFragment()103     public AccountServerBaseFragment() {}
104 
105     /**
106      * At onCreate time, read the fragment arguments
107      */
108     @Override
onCreate(Bundle savedInstanceState)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      */
onCreateViewSettingsMode(View view)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
onActivityCreated(Bundle savedInstanceState)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
onResume()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
onSaveInstanceState(Bundle outState)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
onPause()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
onClick(View v)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      */
enableNextButton(boolean enable)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      */
makeTextViewUneditable(final TextView view, final String errorMessage)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      */
haveSettingsChanged()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 
saveSettings()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 
getSaveSettingsLoader()286     public abstract Loader<Boolean> getSaveSettingsLoader();
287 
288     /**
289      * Collect the user's input into the setup data object.  Concrete classes must implement.
290      */
collectUserInputInternal()291     public abstract int collectUserInputInternal();
292 
collectUserInput()293     public void collectUserInput() {
294         final int phase = collectUserInputInternal();
295         final Callback callback = (Callback) getActivity();
296         callback.onAccountServerUIComplete(phase);
297     }
298 }
299