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