Home | History | Annotate | Download | only in sip
      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.phone.sip;
     18 
     19 import com.android.internal.telephony.CallManager;
     20 import com.android.internal.telephony.Phone;
     21 import com.android.phone.R;
     22 import com.android.phone.SipUtil;
     23 
     24 import android.app.ActionBar;
     25 import android.app.AlertDialog;
     26 import android.content.Intent;
     27 import android.net.sip.SipManager;
     28 import android.net.sip.SipProfile;
     29 import android.os.Bundle;
     30 import android.os.Parcelable;
     31 import android.preference.CheckBoxPreference;
     32 import android.preference.EditTextPreference;
     33 import android.preference.ListPreference;
     34 import android.preference.Preference;
     35 import android.preference.PreferenceActivity;
     36 import android.preference.PreferenceGroup;
     37 import android.text.TextUtils;
     38 import android.util.Log;
     39 import android.view.KeyEvent;
     40 import android.view.Menu;
     41 import android.view.MenuItem;
     42 import android.view.View;
     43 import android.widget.Button;
     44 import android.widget.Toast;
     45 
     46 import java.io.IOException;
     47 import java.lang.reflect.Method;
     48 import java.util.Arrays;
     49 
     50 /**
     51  * The activity class for editing a new or existing SIP profile.
     52  */
     53 public class SipEditor extends PreferenceActivity
     54         implements Preference.OnPreferenceChangeListener {
     55     private static final int MENU_SAVE = Menu.FIRST;
     56     private static final int MENU_DISCARD = Menu.FIRST + 1;
     57 
     58     private static final String TAG = SipEditor.class.getSimpleName();
     59     private static final String KEY_PROFILE = "profile";
     60     private static final String GET_METHOD_PREFIX = "get";
     61     private static final char SCRAMBLED = '*';
     62     private static final int NA = 0;
     63 
     64     private PrimaryAccountSelector mPrimaryAccountSelector;
     65     private AdvancedSettings mAdvancedSettings;
     66     private SipSharedPreferences mSharedPreferences;
     67     private boolean mDisplayNameSet;
     68     private boolean mHomeButtonClicked;
     69     private boolean mUpdateRequired;
     70 
     71     private SipManager mSipManager;
     72     private SipProfileDb mProfileDb;
     73     private SipProfile mOldProfile;
     74     private CallManager mCallManager;
     75     private Button mRemoveButton;
     76 
     77     enum PreferenceKey {
     78         Username(R.string.username, 0, R.string.default_preference_summary),
     79         Password(R.string.password, 0, R.string.default_preference_summary),
     80         DomainAddress(R.string.domain_address, 0, R.string.default_preference_summary),
     81         DisplayName(R.string.display_name, 0, R.string.display_name_summary),
     82         ProxyAddress(R.string.proxy_address, 0, R.string.optional_summary),
     83         Port(R.string.port, R.string.default_port, R.string.default_port),
     84         Transport(R.string.transport, R.string.default_transport, NA),
     85         SendKeepAlive(R.string.send_keepalive, R.string.sip_system_decide, NA),
     86         AuthUserName(R.string.auth_username, 0, R.string.optional_summary);
     87 
     88         final int text;
     89         final int initValue;
     90         final int defaultSummary;
     91         Preference preference;
     92 
     93         /**
     94          * @param key The key name of the preference.
     95          * @param initValue The initial value of the preference.
     96          * @param defaultSummary The default summary value of the preference
     97          *        when the preference value is empty.
     98          */
     99         PreferenceKey(int text, int initValue, int defaultSummary) {
    100             this.text = text;
    101             this.initValue = initValue;
    102             this.defaultSummary = defaultSummary;
    103         }
    104 
    105         String getValue() {
    106             if (preference instanceof EditTextPreference) {
    107                 return ((EditTextPreference) preference).getText();
    108             } else if (preference instanceof ListPreference) {
    109                 return ((ListPreference) preference).getValue();
    110             }
    111             throw new RuntimeException("getValue() for the preference " + this);
    112         }
    113 
    114         void setValue(String value) {
    115             if (preference instanceof EditTextPreference) {
    116                 String oldValue = getValue();
    117                 ((EditTextPreference) preference).setText(value);
    118                 if (this != Password) {
    119                     Log.v(TAG, this + ": setValue() " + value + ": " + oldValue
    120                             + " --> " + getValue());
    121                 }
    122             } else if (preference instanceof ListPreference) {
    123                 ((ListPreference) preference).setValue(value);
    124             }
    125 
    126             if (TextUtils.isEmpty(value)) {
    127                 preference.setSummary(defaultSummary);
    128             } else if (this == Password) {
    129                 preference.setSummary(scramble(value));
    130             } else if ((this == DisplayName)
    131                     && value.equals(getDefaultDisplayName())) {
    132                 preference.setSummary(defaultSummary);
    133             } else {
    134                 preference.setSummary(value);
    135             }
    136         }
    137     }
    138 
    139     @Override
    140     public void onResume() {
    141         super.onResume();
    142         mHomeButtonClicked = false;
    143         if (mCallManager.getState() != Phone.State.IDLE) {
    144             mAdvancedSettings.show();
    145             getPreferenceScreen().setEnabled(false);
    146             if (mRemoveButton != null) mRemoveButton.setEnabled(false);
    147         } else {
    148             getPreferenceScreen().setEnabled(true);
    149             if (mRemoveButton != null) mRemoveButton.setEnabled(true);
    150         }
    151     }
    152 
    153     @Override
    154     public void onCreate(Bundle savedInstanceState) {
    155         Log.v(TAG, "start profile editor");
    156         super.onCreate(savedInstanceState);
    157 
    158         mSipManager = SipManager.newInstance(this);
    159         mSharedPreferences = new SipSharedPreferences(this);
    160         mProfileDb = new SipProfileDb(this);
    161         mCallManager = CallManager.getInstance();
    162 
    163         setContentView(R.layout.sip_settings_ui);
    164         addPreferencesFromResource(R.xml.sip_edit);
    165 
    166         SipProfile p = mOldProfile = (SipProfile) ((savedInstanceState == null)
    167                 ? getIntent().getParcelableExtra(SipSettings.KEY_SIP_PROFILE)
    168                 : savedInstanceState.getParcelable(KEY_PROFILE));
    169 
    170         PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    171         for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
    172             setupPreference(screen.getPreference(i));
    173         }
    174 
    175         if (p == null) {
    176             findViewById(R.id.add_remove_account_bar)
    177                     .setVisibility(View.GONE);
    178             screen.setTitle(R.string.sip_edit_new_title);
    179         } else {
    180             mRemoveButton =
    181                     (Button)findViewById(R.id.add_remove_account_button);
    182             mRemoveButton.setText(getString(R.string.remove_sip_account));
    183             mRemoveButton.setOnClickListener(
    184                     new android.view.View.OnClickListener() {
    185                         public void onClick(View v) {
    186                             setRemovedProfileAndFinish();
    187                         }
    188                     });
    189         }
    190         mAdvancedSettings = new AdvancedSettings();
    191         mPrimaryAccountSelector = new PrimaryAccountSelector(p);
    192 
    193         loadPreferencesFromProfile(p);
    194 
    195         ActionBar actionBar = getActionBar();
    196         if (actionBar != null) {
    197             // android.R.id.home will be triggered in onOptionsItemSelected()
    198             actionBar.setDisplayHomeAsUpEnabled(true);
    199         }
    200     }
    201 
    202     @Override
    203     public void onPause() {
    204         Log.v(TAG, "SipEditor onPause(): finishing? " + isFinishing());
    205         if (!isFinishing()) {
    206             mHomeButtonClicked = true;
    207             validateAndSetResult();
    208         }
    209         super.onPause();
    210     }
    211 
    212     @Override
    213     public boolean onCreateOptionsMenu(Menu menu) {
    214         super.onCreateOptionsMenu(menu);
    215         menu.add(0, MENU_SAVE, 0, R.string.sip_menu_save)
    216                 .setIcon(android.R.drawable.ic_menu_save);
    217         menu.add(0, MENU_DISCARD, 0, R.string.sip_menu_discard)
    218                 .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
    219         return true;
    220     }
    221 
    222     @Override
    223     public boolean onOptionsItemSelected(MenuItem item) {
    224         switch (item.getItemId()) {
    225             case android.R.id.home: // See ActionBar#setDisplayHomeAsUpEnabled()
    226                 // This time just work as "back" or "save" capability.
    227             case MENU_SAVE:
    228                 validateAndSetResult();
    229                 return true;
    230 
    231             case MENU_DISCARD:
    232                 finish();
    233                 return true;
    234         }
    235         return super.onOptionsItemSelected(item);
    236     }
    237 
    238     @Override
    239     public boolean onKeyDown(int keyCode, KeyEvent event) {
    240         switch (keyCode) {
    241             case KeyEvent.KEYCODE_BACK:
    242                 validateAndSetResult();
    243                 return true;
    244         }
    245         return super.onKeyDown(keyCode, event);
    246     }
    247 
    248     private void saveAndRegisterProfile(SipProfile p) throws IOException {
    249         if (p == null) return;
    250         mProfileDb.saveProfile(p);
    251         if (p.getAutoRegistration()
    252                 || mSharedPreferences.isPrimaryAccount(p.getUriString())) {
    253             try {
    254                 mSipManager.open(p, SipUtil.createIncomingCallPendingIntent(),
    255                         null);
    256             } catch (Exception e) {
    257                 Log.e(TAG, "register failed: " + p.getUriString(), e);
    258             }
    259         }
    260     }
    261 
    262     private void deleteAndUnregisterProfile(SipProfile p) {
    263         if (p == null) return;
    264         mProfileDb.deleteProfile(p);
    265         unregisterProfile(p.getUriString());
    266     }
    267 
    268     private void unregisterProfile(String uri) {
    269         try {
    270             mSipManager.close(uri);
    271         } catch (Exception e) {
    272             Log.e(TAG, "unregister failed: " + uri, e);
    273         }
    274     }
    275 
    276     private void setRemovedProfileAndFinish() {
    277         Intent intent = new Intent(this, SipSettings.class);
    278         setResult(RESULT_FIRST_USER, intent);
    279         Toast.makeText(this, R.string.removing_account, Toast.LENGTH_SHORT)
    280                 .show();
    281         replaceProfile(mOldProfile, null);
    282         // do finish() in replaceProfile() in a background thread
    283     }
    284 
    285     private void showAlert(Throwable e) {
    286         String msg = e.getMessage();
    287         if (TextUtils.isEmpty(msg)) msg = e.toString();
    288         showAlert(msg);
    289     }
    290 
    291     private void showAlert(final String message) {
    292         if (mHomeButtonClicked) {
    293             Log.v(TAG, "Home button clicked, don't show dialog: " + message);
    294             return;
    295         }
    296         runOnUiThread(new Runnable() {
    297             public void run() {
    298                 new AlertDialog.Builder(SipEditor.this)
    299                         .setTitle(android.R.string.dialog_alert_title)
    300                         .setIcon(android.R.drawable.ic_dialog_alert)
    301                         .setMessage(message)
    302                         .setPositiveButton(R.string.alert_dialog_ok, null)
    303                         .show();
    304             }
    305         });
    306     }
    307 
    308     private boolean isEditTextEmpty(PreferenceKey key) {
    309         EditTextPreference pref = (EditTextPreference) key.preference;
    310         return TextUtils.isEmpty(pref.getText())
    311                 || pref.getSummary().equals(getString(key.defaultSummary));
    312     }
    313 
    314     private void validateAndSetResult() {
    315         boolean allEmpty = true;
    316         CharSequence firstEmptyFieldTitle = null;
    317         for (PreferenceKey key : PreferenceKey.values()) {
    318             Preference p = key.preference;
    319             if (p instanceof EditTextPreference) {
    320                 EditTextPreference pref = (EditTextPreference) p;
    321                 boolean fieldEmpty = isEditTextEmpty(key);
    322                 if (allEmpty && !fieldEmpty) allEmpty = false;
    323 
    324                 // use default value if display name is empty
    325                 if (fieldEmpty) {
    326                     switch (key) {
    327                         case DisplayName:
    328                             pref.setText(getDefaultDisplayName());
    329                             break;
    330                         case AuthUserName:
    331                         case ProxyAddress:
    332                             // optional; do nothing
    333                             break;
    334                         case Port:
    335                             pref.setText(getString(R.string.default_port));
    336                             break;
    337                         default:
    338                             if (firstEmptyFieldTitle == null) {
    339                                 firstEmptyFieldTitle = pref.getTitle();
    340                             }
    341                     }
    342                 } else if (key == PreferenceKey.Port) {
    343                     int port = Integer.parseInt(PreferenceKey.Port.getValue());
    344                     if ((port < 1000) || (port > 65534)) {
    345                         showAlert(getString(R.string.not_a_valid_port));
    346                         return;
    347                     }
    348                 }
    349             }
    350         }
    351 
    352         if (allEmpty || !mUpdateRequired) {
    353             finish();
    354             return;
    355         } else if (firstEmptyFieldTitle != null) {
    356             showAlert(getString(R.string.empty_alert, firstEmptyFieldTitle));
    357             return;
    358         }
    359         try {
    360             SipProfile profile = createSipProfile();
    361             Intent intent = new Intent(this, SipSettings.class);
    362             intent.putExtra(SipSettings.KEY_SIP_PROFILE, (Parcelable) profile);
    363             setResult(RESULT_OK, intent);
    364             Toast.makeText(this, R.string.saving_account, Toast.LENGTH_SHORT)
    365                     .show();
    366 
    367             replaceProfile(mOldProfile, profile);
    368             // do finish() in replaceProfile() in a background thread
    369         } catch (Exception e) {
    370             Log.w(TAG, "Can not create new SipProfile", e);
    371             showAlert(e);
    372         }
    373     }
    374 
    375     private void unregisterOldPrimaryAccount() {
    376         String primaryAccountUri = mSharedPreferences.getPrimaryAccount();
    377         Log.v(TAG, "old primary: " + primaryAccountUri);
    378         if ((primaryAccountUri != null)
    379                 && !mSharedPreferences.isReceivingCallsEnabled()) {
    380             Log.v(TAG, "unregister old primary: " + primaryAccountUri);
    381             unregisterProfile(primaryAccountUri);
    382         }
    383     }
    384 
    385     private void replaceProfile(final SipProfile oldProfile,
    386             final SipProfile newProfile) {
    387         // Replace profile in a background thread as it takes time to access the
    388         // storage; do finish() once everything goes fine.
    389         // newProfile may be null if the old profile is to be deleted rather
    390         // than being modified.
    391         new Thread(new Runnable() {
    392             public void run() {
    393                 try {
    394                     // if new profile is primary, unregister the old primary account
    395                     if ((newProfile != null) && mPrimaryAccountSelector.isSelected()) {
    396                         unregisterOldPrimaryAccount();
    397                     }
    398 
    399                     mPrimaryAccountSelector.commit(newProfile);
    400                     deleteAndUnregisterProfile(oldProfile);
    401                     saveAndRegisterProfile(newProfile);
    402                     finish();
    403                 } catch (Exception e) {
    404                     Log.e(TAG, "Can not save/register new SipProfile", e);
    405                     showAlert(e);
    406                 }
    407             }
    408         }, "SipEditor").start();
    409     }
    410 
    411     private String getProfileName() {
    412         return PreferenceKey.Username.getValue() + "@"
    413                 + PreferenceKey.DomainAddress.getValue();
    414     }
    415 
    416     private SipProfile createSipProfile() throws Exception {
    417             return new SipProfile.Builder(
    418                     PreferenceKey.Username.getValue(),
    419                     PreferenceKey.DomainAddress.getValue())
    420                     .setProfileName(getProfileName())
    421                     .setPassword(PreferenceKey.Password.getValue())
    422                     .setOutboundProxy(PreferenceKey.ProxyAddress.getValue())
    423                     .setProtocol(PreferenceKey.Transport.getValue())
    424                     .setDisplayName(PreferenceKey.DisplayName.getValue())
    425                     .setPort(Integer.parseInt(PreferenceKey.Port.getValue()))
    426                     .setSendKeepAlive(isAlwaysSendKeepAlive())
    427                     .setAutoRegistration(
    428                             mSharedPreferences.isReceivingCallsEnabled())
    429                     .setAuthUserName(PreferenceKey.AuthUserName.getValue())
    430                     .build();
    431     }
    432 
    433     public boolean onPreferenceChange(Preference pref, Object newValue) {
    434         if (!mUpdateRequired) {
    435             mUpdateRequired = true;
    436             if (mOldProfile != null) {
    437                 unregisterProfile(mOldProfile.getUriString());
    438             }
    439         }
    440         if (pref instanceof CheckBoxPreference) return true;
    441         String value = (newValue == null) ? "" : newValue.toString();
    442         if (TextUtils.isEmpty(value)) {
    443             pref.setSummary(getPreferenceKey(pref).defaultSummary);
    444         } else if (pref == PreferenceKey.Password.preference) {
    445             pref.setSummary(scramble(value));
    446         } else {
    447             pref.setSummary(value);
    448         }
    449 
    450         if (pref == PreferenceKey.DisplayName.preference) {
    451             ((EditTextPreference) pref).setText(value);
    452             checkIfDisplayNameSet();
    453         }
    454         return true;
    455     }
    456 
    457     private PreferenceKey getPreferenceKey(Preference pref) {
    458         for (PreferenceKey key : PreferenceKey.values()) {
    459             if (key.preference == pref) return key;
    460         }
    461         throw new RuntimeException("not possible to reach here");
    462     }
    463 
    464     private void loadPreferencesFromProfile(SipProfile p) {
    465         if (p != null) {
    466             Log.v(TAG, "Edit the existing profile : " + p.getProfileName());
    467             try {
    468                 Class profileClass = SipProfile.class;
    469                 for (PreferenceKey key : PreferenceKey.values()) {
    470                     Method meth = profileClass.getMethod(GET_METHOD_PREFIX
    471                             + getString(key.text), (Class[])null);
    472                     if (key == PreferenceKey.SendKeepAlive) {
    473                         boolean value = ((Boolean)
    474                                 meth.invoke(p, (Object[]) null)).booleanValue();
    475                         key.setValue(getString(value
    476                                 ? R.string.sip_always_send_keepalive
    477                                 : R.string.sip_system_decide));
    478                     } else {
    479                         Object value = meth.invoke(p, (Object[])null);
    480                         key.setValue((value == null) ? "" : value.toString());
    481                     }
    482                 }
    483                 checkIfDisplayNameSet();
    484             } catch (Exception e) {
    485                 Log.e(TAG, "Can not load pref from profile", e);
    486             }
    487         } else {
    488             Log.v(TAG, "Edit a new profile");
    489             for (PreferenceKey key : PreferenceKey.values()) {
    490                 key.preference.setOnPreferenceChangeListener(this);
    491 
    492                 // FIXME: android:defaultValue in preference xml file doesn't
    493                 // work. Even if we setValue() for each preference in the case
    494                 // of (p != null), the dialog still shows android:defaultValue,
    495                 // not the value set by setValue(). This happens if
    496                 // android:defaultValue is not empty. Is it a bug?
    497                 if (key.initValue != 0) {
    498                     key.setValue(getString(key.initValue));
    499                 }
    500             }
    501             mDisplayNameSet = false;
    502         }
    503     }
    504 
    505     private boolean isAlwaysSendKeepAlive() {
    506         ListPreference pref = (ListPreference)
    507                 PreferenceKey.SendKeepAlive.preference;
    508         return getString(R.string.sip_always_send_keepalive).equals(
    509                 pref.getValue());
    510     }
    511 
    512     private void setCheckBox(PreferenceKey key, boolean checked) {
    513         CheckBoxPreference pref = (CheckBoxPreference) key.preference;
    514         pref.setChecked(checked);
    515     }
    516 
    517     private void setupPreference(Preference pref) {
    518         pref.setOnPreferenceChangeListener(this);
    519         for (PreferenceKey key : PreferenceKey.values()) {
    520             String name = getString(key.text);
    521             if (name.equals(pref.getKey())) {
    522                 key.preference = pref;
    523                 return;
    524             }
    525         }
    526     }
    527 
    528     private void checkIfDisplayNameSet() {
    529         String displayName = PreferenceKey.DisplayName.getValue();
    530         mDisplayNameSet = !TextUtils.isEmpty(displayName)
    531                 && !displayName.equals(getDefaultDisplayName());
    532         Log.d(TAG, "displayName set? " + mDisplayNameSet);
    533         if (mDisplayNameSet) {
    534             PreferenceKey.DisplayName.preference.setSummary(displayName);
    535         } else {
    536             PreferenceKey.DisplayName.setValue("");
    537         }
    538     }
    539 
    540     private static String getDefaultDisplayName() {
    541         return PreferenceKey.Username.getValue();
    542     }
    543 
    544     private static String scramble(String s) {
    545         char[] cc = new char[s.length()];
    546         Arrays.fill(cc, SCRAMBLED);
    547         return new String(cc);
    548     }
    549 
    550     // only takes care of the primary account setting in SipSharedSettings
    551     private class PrimaryAccountSelector {
    552         private CheckBoxPreference mCheckbox;
    553         private final boolean mWasPrimaryAccount;
    554 
    555         // @param profile profile to be edited; null if adding new profile
    556         PrimaryAccountSelector(SipProfile profile) {
    557             mCheckbox = (CheckBoxPreference) getPreferenceScreen()
    558                     .findPreference(getString(R.string.set_primary));
    559             boolean noPrimaryAccountSet =
    560                     !mSharedPreferences.hasPrimaryAccount();
    561             boolean editNewProfile = (profile == null);
    562             mWasPrimaryAccount = !editNewProfile
    563                     && mSharedPreferences.isPrimaryAccount(
    564                             profile.getUriString());
    565 
    566             Log.v(TAG, " noPrimaryAccountSet: " + noPrimaryAccountSet);
    567             Log.v(TAG, " editNewProfile: " + editNewProfile);
    568             Log.v(TAG, " mWasPrimaryAccount: " + mWasPrimaryAccount);
    569 
    570             mCheckbox.setChecked(mWasPrimaryAccount
    571                     || (editNewProfile && noPrimaryAccountSet));
    572         }
    573 
    574         boolean isSelected() {
    575             return mCheckbox.isChecked();
    576         }
    577 
    578         // profile is null if the user removes it
    579         void commit(SipProfile profile) {
    580             if ((profile != null) && mCheckbox.isChecked()) {
    581                 mSharedPreferences.setPrimaryAccount(profile.getUriString());
    582             } else if (mWasPrimaryAccount) {
    583                 mSharedPreferences.unsetPrimaryAccount();
    584             }
    585             Log.d(TAG, " primary account changed to : "
    586                     + mSharedPreferences.getPrimaryAccount());
    587         }
    588     }
    589 
    590     private class AdvancedSettings
    591             implements Preference.OnPreferenceClickListener {
    592         private Preference mAdvancedSettingsTrigger;
    593         private Preference[] mPreferences;
    594         private boolean mShowing = false;
    595 
    596         AdvancedSettings() {
    597             mAdvancedSettingsTrigger = getPreferenceScreen().findPreference(
    598                     getString(R.string.advanced_settings));
    599             mAdvancedSettingsTrigger.setOnPreferenceClickListener(this);
    600 
    601             loadAdvancedPreferences();
    602         }
    603 
    604         private void loadAdvancedPreferences() {
    605             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    606 
    607             addPreferencesFromResource(R.xml.sip_advanced_edit);
    608             PreferenceGroup group = (PreferenceGroup) screen.findPreference(
    609                     getString(R.string.advanced_settings_container));
    610             screen.removePreference(group);
    611 
    612             mPreferences = new Preference[group.getPreferenceCount()];
    613             int order = screen.getPreferenceCount();
    614             for (int i = 0, n = mPreferences.length; i < n; i++) {
    615                 Preference pref = group.getPreference(i);
    616                 pref.setOrder(order++);
    617                 setupPreference(pref);
    618                 mPreferences[i] = pref;
    619             }
    620         }
    621 
    622         void show() {
    623             mShowing = true;
    624             mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_hide);
    625             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    626             for (Preference pref : mPreferences) {
    627                 screen.addPreference(pref);
    628                 Log.v(TAG, "add pref " + pref.getKey() + ": order=" + pref.getOrder());
    629             }
    630         }
    631 
    632         private void hide() {
    633             mShowing = false;
    634             mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_show);
    635             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    636             for (Preference pref : mPreferences) {
    637                 screen.removePreference(pref);
    638             }
    639         }
    640 
    641         public boolean onPreferenceClick(Preference preference) {
    642             Log.v(TAG, "optional settings clicked");
    643             if (!mShowing) {
    644                 show();
    645             } else {
    646                 hide();
    647             }
    648             return true;
    649         }
    650     }
    651 }
    652