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