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 android.app.Activity;
20import android.app.LoaderManager;
21import android.content.Context;
22import android.content.Loader;
23import android.os.Bundle;
24import android.os.Handler;
25import android.view.View;
26import android.view.View.OnClickListener;
27import android.view.View.OnFocusChangeListener;
28import android.view.inputmethod.InputMethodManager;
29import android.widget.TextView;
30
31import com.android.email.R;
32import com.android.email.activity.UiUtilities;
33import com.android.emailcommon.provider.Account;
34import 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 */
44public 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