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