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.internal.telephony.PhoneConstants;
     22 import com.android.phone.R;
     23 import com.android.phone.SipUtil;
     24 
     25 import android.app.ActionBar;
     26 import android.app.AlertDialog;
     27 import android.content.Intent;
     28 import android.net.sip.SipManager;
     29 import android.net.sip.SipProfile;
     30 import android.os.Bundle;
     31 import android.os.Parcelable;
     32 import android.preference.CheckBoxPreference;
     33 import android.preference.EditTextPreference;
     34 import android.preference.ListPreference;
     35 import android.preference.Preference;
     36 import android.preference.PreferenceActivity;
     37 import android.preference.PreferenceGroup;
     38 import android.text.TextUtils;
     39 import android.util.Log;
     40 import android.view.KeyEvent;
     41 import android.view.Menu;
     42 import android.view.MenuItem;
     43 import android.view.View;
     44 import android.widget.Button;
     45 import android.widget.Toast;
     46 
     47 import java.io.IOException;
     48 import java.lang.reflect.Method;
     49 import java.util.Arrays;
     50 
     51 /**
     52  * The activity class for editing a new or existing SIP profile.
     53  */
     54 public class SipEditor extends PreferenceActivity
     55         implements Preference.OnPreferenceChangeListener {
     56     private static final int MENU_SAVE = Menu.FIRST;
     57     private static final int MENU_DISCARD = Menu.FIRST + 1;
     58     private static final int MENU_REMOVE = Menu.FIRST + 2;
     59 
     60     private static final String TAG = SipEditor.class.getSimpleName();
     61     private static final String KEY_PROFILE = "profile";
     62     private static final String GET_METHOD_PREFIX = "get";
     63     private static final char SCRAMBLED = '*';
     64     private static final int NA = 0;
     65 
     66     private PrimaryAccountSelector mPrimaryAccountSelector;
     67     private AdvancedSettings mAdvancedSettings;
     68     private SipSharedPreferences mSharedPreferences;
     69     private boolean mDisplayNameSet;
     70     private boolean mHomeButtonClicked;
     71     private boolean mUpdateRequired;
     72     private boolean mUpdatedFieldIsEmpty;
     73 
     74     private SipManager mSipManager;
     75     private SipProfileDb mProfileDb;
     76     private SipProfile mOldProfile;
     77     private CallManager mCallManager;
     78     private Button mRemoveButton;
     79 
     80     enum PreferenceKey {
     81         Username(R.string.username, 0, R.string.default_preference_summary),
     82         Password(R.string.password, 0, R.string.default_preference_summary),
     83         DomainAddress(R.string.domain_address, 0, R.string.default_preference_summary),
     84         DisplayName(R.string.display_name, 0, R.string.display_name_summary),
     85         ProxyAddress(R.string.proxy_address, 0, R.string.optional_summary),
     86         Port(R.string.port, R.string.default_port, R.string.default_port),
     87         Transport(R.string.transport, R.string.default_transport, NA),
     88         SendKeepAlive(R.string.send_keepalive, R.string.sip_system_decide, NA),
     89         AuthUserName(R.string.auth_username, 0, R.string.optional_summary);
     90 
     91         final int text;
     92         final int initValue;
     93         final int defaultSummary;
     94         Preference preference;
     95 
     96         /**
     97          * @param key The key name of the preference.
     98          * @param initValue The initial value of the preference.
     99          * @param defaultSummary The default summary value of the preference
    100          *        when the preference value is empty.
    101          */
    102         PreferenceKey(int text, int initValue, int defaultSummary) {
    103             this.text = text;
    104             this.initValue = initValue;
    105             this.defaultSummary = defaultSummary;
    106         }
    107 
    108         String getValue() {
    109             if (preference instanceof EditTextPreference) {
    110                 return ((EditTextPreference) preference).getText();
    111             } else if (preference instanceof ListPreference) {
    112                 return ((ListPreference) preference).getValue();
    113             }
    114             throw new RuntimeException("getValue() for the preference " + this);
    115         }
    116 
    117         void setValue(String value) {
    118             if (preference instanceof EditTextPreference) {
    119                 String oldValue = getValue();
    120                 ((EditTextPreference) preference).setText(value);
    121                 if (this != Password) {
    122                     Log.v(TAG, this + ": setValue() " + value + ": " + oldValue
    123                             + " --> " + getValue());
    124                 }
    125             } else if (preference instanceof ListPreference) {
    126                 ((ListPreference) preference).setValue(value);
    127             }
    128 
    129             if (TextUtils.isEmpty(value)) {
    130                 preference.setSummary(defaultSummary);
    131             } else if (this == Password) {
    132                 preference.setSummary(scramble(value));
    133             } else if ((this == DisplayName)
    134                     && value.equals(getDefaultDisplayName())) {
    135                 preference.setSummary(defaultSummary);
    136             } else {
    137                 preference.setSummary(value);
    138             }
    139         }
    140     }
    141 
    142     @Override
    143     public void onResume() {
    144         super.onResume();
    145         mHomeButtonClicked = false;
    146         if (mCallManager.getState() != PhoneConstants.State.IDLE) {
    147             mAdvancedSettings.show();
    148             getPreferenceScreen().setEnabled(false);
    149             if (mRemoveButton != null) mRemoveButton.setEnabled(false);
    150         } else {
    151             getPreferenceScreen().setEnabled(true);
    152             if (mRemoveButton != null) mRemoveButton.setEnabled(true);
    153         }
    154     }
    155 
    156     @Override
    157     public void onCreate(Bundle savedInstanceState) {
    158         Log.v(TAG, "start profile editor");
    159         super.onCreate(savedInstanceState);
    160 
    161         mSipManager = SipManager.newInstance(this);
    162         mSharedPreferences = new SipSharedPreferences(this);
    163         mProfileDb = new SipProfileDb(this);
    164         mCallManager = CallManager.getInstance();
    165 
    166         setContentView(R.layout.sip_settings_ui);
    167         addPreferencesFromResource(R.xml.sip_edit);
    168 
    169         SipProfile p = mOldProfile = (SipProfile) ((savedInstanceState == null)
    170                 ? getIntent().getParcelableExtra(SipSettings.KEY_SIP_PROFILE)
    171                 : savedInstanceState.getParcelable(KEY_PROFILE));
    172 
    173         PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    174         for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
    175             setupPreference(screen.getPreference(i));
    176         }
    177 
    178         if (p == null) {
    179             screen.setTitle(R.string.sip_edit_new_title);
    180         }
    181 
    182         mAdvancedSettings = new AdvancedSettings();
    183         mPrimaryAccountSelector = new PrimaryAccountSelector(p);
    184 
    185         loadPreferencesFromProfile(p);
    186     }
    187 
    188     @Override
    189     public void onPause() {
    190         Log.v(TAG, "SipEditor onPause(): finishing? " + isFinishing());
    191         if (!isFinishing()) {
    192             mHomeButtonClicked = true;
    193             validateAndSetResult();
    194         }
    195         super.onPause();
    196     }
    197 
    198     @Override
    199     public boolean onCreateOptionsMenu(Menu menu) {
    200         super.onCreateOptionsMenu(menu);
    201         menu.add(0, MENU_DISCARD, 0, R.string.sip_menu_discard)
    202                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    203         menu.add(0, MENU_SAVE, 0, R.string.sip_menu_save)
    204                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    205         menu.add(0, MENU_REMOVE, 0, R.string.remove_sip_account)
    206                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    207         return true;
    208     }
    209 
    210     @Override
    211     public boolean onPrepareOptionsMenu(Menu menu) {
    212         MenuItem removeMenu = menu.findItem(MENU_REMOVE);
    213         removeMenu.setVisible(mOldProfile != null);
    214         menu.findItem(MENU_SAVE).setEnabled(mUpdateRequired);
    215         return super.onPrepareOptionsMenu(menu);
    216     }
    217 
    218     @Override
    219     public boolean onOptionsItemSelected(MenuItem item) {
    220         switch (item.getItemId()) {
    221             case MENU_SAVE:
    222                 validateAndSetResult();
    223                 return true;
    224 
    225             case MENU_DISCARD:
    226                 finish();
    227                 return true;
    228 
    229             case MENU_REMOVE: {
    230                 setRemovedProfileAndFinish();
    231                 return true;
    232             }
    233         }
    234         return super.onOptionsItemSelected(item);
    235     }
    236 
    237     @Override
    238     public boolean onKeyDown(int keyCode, KeyEvent event) {
    239         switch (keyCode) {
    240             case KeyEvent.KEYCODE_BACK:
    241                 validateAndSetResult();
    242                 return true;
    243         }
    244         return super.onKeyDown(keyCode, event);
    245     }
    246 
    247     private void saveAndRegisterProfile(SipProfile p) throws IOException {
    248         if (p == null) return;
    249         mProfileDb.saveProfile(p);
    250         if (p.getAutoRegistration()
    251                 || mSharedPreferences.isPrimaryAccount(p.getUriString())) {
    252             try {
    253                 mSipManager.open(p, SipUtil.createIncomingCallPendingIntent(),
    254                         null);
    255             } catch (Exception e) {
    256                 Log.e(TAG, "register failed: " + p.getUriString(), e);
    257             }
    258         }
    259     }
    260 
    261     private void deleteAndUnregisterProfile(SipProfile p) {
    262         if (p == null) return;
    263         mProfileDb.deleteProfile(p);
    264         unregisterProfile(p.getUriString());
    265     }
    266 
    267     private void unregisterProfile(String uri) {
    268         try {
    269             mSipManager.close(uri);
    270         } catch (Exception e) {
    271             Log.e(TAG, "unregister failed: " + uri, e);
    272         }
    273     }
    274 
    275     private void setRemovedProfileAndFinish() {
    276         Intent intent = new Intent(this, SipSettings.class);
    277         setResult(RESULT_FIRST_USER, intent);
    278         Toast.makeText(this, R.string.removing_account, Toast.LENGTH_SHORT)
    279                 .show();
    280         replaceProfile(mOldProfile, null);
    281         // do finish() in replaceProfile() in a background thread
    282     }
    283 
    284     private void showAlert(Throwable e) {
    285         String msg = e.getMessage();
    286         if (TextUtils.isEmpty(msg)) msg = e.toString();
    287         showAlert(msg);
    288     }
    289 
    290     private void showAlert(final String message) {
    291         if (mHomeButtonClicked) {
    292             Log.v(TAG, "Home button clicked, don't show dialog: " + message);
    293             return;
    294         }
    295         runOnUiThread(new Runnable() {
    296             @Override
    297             public void run() {
    298                 new AlertDialog.Builder(SipEditor.this)
    299                         .setTitle(android.R.string.dialog_alert_title)
    300                         .setIconAttribute(android.R.attr.alertDialogIcon)
    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) {
    441             invalidateOptionsMenu();
    442             return true;
    443         }
    444         String value = (newValue == null) ? "" : newValue.toString();
    445         if (TextUtils.isEmpty(value)) {
    446             pref.setSummary(getPreferenceKey(pref).defaultSummary);
    447         } else if (pref == PreferenceKey.Password.preference) {
    448             pref.setSummary(scramble(value));
    449         } else {
    450             pref.setSummary(value);
    451         }
    452 
    453         if (pref == PreferenceKey.DisplayName.preference) {
    454             ((EditTextPreference) pref).setText(value);
    455             checkIfDisplayNameSet();
    456         }
    457 
    458         // SAVE menu should be enabled once the user modified some preference.
    459         invalidateOptionsMenu();
    460         return true;
    461     }
    462 
    463     private PreferenceKey getPreferenceKey(Preference pref) {
    464         for (PreferenceKey key : PreferenceKey.values()) {
    465             if (key.preference == pref) return key;
    466         }
    467         throw new RuntimeException("not possible to reach here");
    468     }
    469 
    470     private void loadPreferencesFromProfile(SipProfile p) {
    471         if (p != null) {
    472             Log.v(TAG, "Edit the existing profile : " + p.getProfileName());
    473             try {
    474                 Class profileClass = SipProfile.class;
    475                 for (PreferenceKey key : PreferenceKey.values()) {
    476                     Method meth = profileClass.getMethod(GET_METHOD_PREFIX
    477                             + getString(key.text), (Class[])null);
    478                     if (key == PreferenceKey.SendKeepAlive) {
    479                         boolean value = ((Boolean)
    480                                 meth.invoke(p, (Object[]) null)).booleanValue();
    481                         key.setValue(getString(value
    482                                 ? R.string.sip_always_send_keepalive
    483                                 : R.string.sip_system_decide));
    484                     } else {
    485                         Object value = meth.invoke(p, (Object[])null);
    486                         key.setValue((value == null) ? "" : value.toString());
    487                     }
    488                 }
    489                 checkIfDisplayNameSet();
    490             } catch (Exception e) {
    491                 Log.e(TAG, "Can not load pref from profile", e);
    492             }
    493         } else {
    494             Log.v(TAG, "Edit a new profile");
    495             for (PreferenceKey key : PreferenceKey.values()) {
    496                 key.preference.setOnPreferenceChangeListener(this);
    497 
    498                 // FIXME: android:defaultValue in preference xml file doesn't
    499                 // work. Even if we setValue() for each preference in the case
    500                 // of (p != null), the dialog still shows android:defaultValue,
    501                 // not the value set by setValue(). This happens if
    502                 // android:defaultValue is not empty. Is it a bug?
    503                 if (key.initValue != 0) {
    504                     key.setValue(getString(key.initValue));
    505                 }
    506             }
    507             mDisplayNameSet = false;
    508         }
    509     }
    510 
    511     private boolean isAlwaysSendKeepAlive() {
    512         ListPreference pref = (ListPreference)
    513                 PreferenceKey.SendKeepAlive.preference;
    514         return getString(R.string.sip_always_send_keepalive).equals(
    515                 pref.getValue());
    516     }
    517 
    518     private void setCheckBox(PreferenceKey key, boolean checked) {
    519         CheckBoxPreference pref = (CheckBoxPreference) key.preference;
    520         pref.setChecked(checked);
    521     }
    522 
    523     private void setupPreference(Preference pref) {
    524         pref.setOnPreferenceChangeListener(this);
    525         for (PreferenceKey key : PreferenceKey.values()) {
    526             String name = getString(key.text);
    527             if (name.equals(pref.getKey())) {
    528                 key.preference = pref;
    529                 return;
    530             }
    531         }
    532     }
    533 
    534     private void checkIfDisplayNameSet() {
    535         String displayName = PreferenceKey.DisplayName.getValue();
    536         mDisplayNameSet = !TextUtils.isEmpty(displayName)
    537                 && !displayName.equals(getDefaultDisplayName());
    538         Log.d(TAG, "displayName set? " + mDisplayNameSet);
    539         if (mDisplayNameSet) {
    540             PreferenceKey.DisplayName.preference.setSummary(displayName);
    541         } else {
    542             PreferenceKey.DisplayName.setValue("");
    543         }
    544     }
    545 
    546     private static String getDefaultDisplayName() {
    547         return PreferenceKey.Username.getValue();
    548     }
    549 
    550     private static String scramble(String s) {
    551         char[] cc = new char[s.length()];
    552         Arrays.fill(cc, SCRAMBLED);
    553         return new String(cc);
    554     }
    555 
    556     // only takes care of the primary account setting in SipSharedSettings
    557     private class PrimaryAccountSelector {
    558         private CheckBoxPreference mCheckbox;
    559         private final boolean mWasPrimaryAccount;
    560 
    561         // @param profile profile to be edited; null if adding new profile
    562         PrimaryAccountSelector(SipProfile profile) {
    563             mCheckbox = (CheckBoxPreference) getPreferenceScreen()
    564                     .findPreference(getString(R.string.set_primary));
    565             boolean noPrimaryAccountSet =
    566                     !mSharedPreferences.hasPrimaryAccount();
    567             boolean editNewProfile = (profile == null);
    568             mWasPrimaryAccount = !editNewProfile
    569                     && mSharedPreferences.isPrimaryAccount(
    570                             profile.getUriString());
    571 
    572             Log.v(TAG, " noPrimaryAccountSet: " + noPrimaryAccountSet);
    573             Log.v(TAG, " editNewProfile: " + editNewProfile);
    574             Log.v(TAG, " mWasPrimaryAccount: " + mWasPrimaryAccount);
    575 
    576             mCheckbox.setChecked(mWasPrimaryAccount
    577                     || (editNewProfile && noPrimaryAccountSet));
    578         }
    579 
    580         boolean isSelected() {
    581             return mCheckbox.isChecked();
    582         }
    583 
    584         // profile is null if the user removes it
    585         void commit(SipProfile profile) {
    586             if ((profile != null) && mCheckbox.isChecked()) {
    587                 mSharedPreferences.setPrimaryAccount(profile.getUriString());
    588             } else if (mWasPrimaryAccount) {
    589                 mSharedPreferences.unsetPrimaryAccount();
    590             }
    591             Log.d(TAG, " primary account changed to : "
    592                     + mSharedPreferences.getPrimaryAccount());
    593         }
    594     }
    595 
    596     private class AdvancedSettings
    597             implements Preference.OnPreferenceClickListener {
    598         private Preference mAdvancedSettingsTrigger;
    599         private Preference[] mPreferences;
    600         private boolean mShowing = false;
    601 
    602         AdvancedSettings() {
    603             mAdvancedSettingsTrigger = getPreferenceScreen().findPreference(
    604                     getString(R.string.advanced_settings));
    605             mAdvancedSettingsTrigger.setOnPreferenceClickListener(this);
    606 
    607             loadAdvancedPreferences();
    608         }
    609 
    610         private void loadAdvancedPreferences() {
    611             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    612 
    613             addPreferencesFromResource(R.xml.sip_advanced_edit);
    614             PreferenceGroup group = (PreferenceGroup) screen.findPreference(
    615                     getString(R.string.advanced_settings_container));
    616             screen.removePreference(group);
    617 
    618             mPreferences = new Preference[group.getPreferenceCount()];
    619             int order = screen.getPreferenceCount();
    620             for (int i = 0, n = mPreferences.length; i < n; i++) {
    621                 Preference pref = group.getPreference(i);
    622                 pref.setOrder(order++);
    623                 setupPreference(pref);
    624                 mPreferences[i] = pref;
    625             }
    626         }
    627 
    628         void show() {
    629             mShowing = true;
    630             mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_hide);
    631             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    632             for (Preference pref : mPreferences) {
    633                 screen.addPreference(pref);
    634                 Log.v(TAG, "add pref " + pref.getKey() + ": order=" + pref.getOrder());
    635             }
    636         }
    637 
    638         private void hide() {
    639             mShowing = false;
    640             mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_show);
    641             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
    642             for (Preference pref : mPreferences) {
    643                 screen.removePreference(pref);
    644             }
    645         }
    646 
    647         public boolean onPreferenceClick(Preference preference) {
    648             Log.v(TAG, "optional settings clicked");
    649             if (!mShowing) {
    650                 show();
    651             } else {
    652                 hide();
    653             }
    654             return true;
    655         }
    656     }
    657 }
    658