ContactsPreferenceActivity.java revision 35769b804fbfd5a1fc0b2c36cd0a786d662c4334
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.preference;
18
19import com.android.contacts.ContactsSearchManager;
20import com.android.contacts.R;
21import com.android.contacts.model.BaseAccountType;
22import com.android.contacts.model.EntityDelta.ValuesDelta;
23import com.android.contacts.model.GoogleAccountType;
24import com.android.contacts.model.AccountTypes;
25import com.android.contacts.util.EmptyService;
26import com.android.contacts.util.LocalizedNameResolver;
27import com.android.contacts.util.WeakAsyncTask;
28import com.google.android.collect.Lists;
29
30import android.accounts.Account;
31import android.app.Activity;
32import android.app.AlertDialog;
33import android.app.Dialog;
34import android.app.ExpandableListActivity;
35import android.app.ProgressDialog;
36import android.content.ContentProviderOperation;
37import android.content.ContentProviderOperation.Builder;
38import android.content.ContentResolver;
39import android.content.ContentValues;
40import android.content.Context;
41import android.content.DialogInterface;
42import android.content.EntityIterator;
43import android.content.Intent;
44import android.content.OperationApplicationException;
45import android.content.SharedPreferences;
46import android.content.SharedPreferences.Editor;
47import android.database.Cursor;
48import android.net.Uri;
49import android.os.Bundle;
50import android.os.RemoteException;
51import android.preference.PreferenceActivity;
52import android.preference.PreferenceActivity.Header;
53import android.preference.PreferenceManager;
54import android.provider.ContactsContract;
55import android.provider.ContactsContract.Groups;
56import android.provider.ContactsContract.Settings;
57import android.util.Log;
58import android.view.ContextMenu;
59import android.view.LayoutInflater;
60import android.view.MenuItem;
61import android.view.MenuItem.OnMenuItemClickListener;
62import android.view.View;
63import android.view.ViewGroup;
64import android.widget.AdapterView;
65import android.widget.BaseExpandableListAdapter;
66import android.widget.CheckBox;
67import android.widget.ExpandableListAdapter;
68import android.widget.ExpandableListView;
69import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
70import android.widget.ListView;
71import android.widget.TextView;
72
73import java.lang.ref.WeakReference;
74import java.util.ArrayList;
75import java.util.Collections;
76import java.util.Comparator;
77import java.util.Iterator;
78import java.util.List;
79
80/**
81 * Contacts settings.
82 */
83public final class ContactsPreferenceActivity extends PreferenceActivity
84//implements
85//        AdapterView.OnItemClickListener, View.OnClickListener
86        {
87    private static final String TAG = "ContactsSettingsActivity";
88
89    /**
90     * Populate the activity with the top-level headers.
91     */
92    @Override
93    public void onBuildHeaders(List<Header> target) {
94        loadHeadersFromResource(R.xml.preference_headers, target);
95    }
96
97
98//    public interface Prefs {
99//        public static final String DISPLAY_ONLY_PHONES = "only_phones";
100//        public static final boolean DISPLAY_ONLY_PHONES_DEFAULT = false;
101//
102//    }
103//
104//    private static final int DIALOG_SORT_ORDER = 1;
105//    private static final int DIALOG_DISPLAY_ORDER = 2;
106//
107//    private ExpandableListView mList;
108//    private DisplayAdapter mAdapter;
109//
110//    private SharedPreferences mPrefs;
111//    private ContactsPreferences mContactsPrefs;
112//
113//    private CheckBox mDisplayPhones;
114//
115//    private View mHeaderPhones;
116//    private View mHeaderSeparator;
117//
118//    private View mSortOrderView;
119//    private TextView mSortOrderTextView;
120//    private int mSortOrder;
121//
122//    private View mDisplayOrderView;
123//    private TextView mDisplayOrderTextView;
124//    private int mDisplayOrder;
125//
126//    @Override
127//    protected void onCreate(Bundle icicle) {
128//        super.onCreate(icicle);
129//        setContentView(R.layout.contacts_preferences);
130//
131//        mList = getExpandableListView();
132//        mList.setHeaderDividersEnabled(true);
133//        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
134//        mContactsPrefs = new ContactsPreferences(this);
135//        mAdapter = new DisplayAdapter(this);
136//
137//        final LayoutInflater inflater = getLayoutInflater();
138//
139//        createWithPhonesOnlyPreferenceView(inflater);
140//        createSortOrderPreferenceView(inflater);
141//        createDisplayOrderPreferenceView(inflater);
142//        createDisplayGroupHeader(inflater);
143//
144//        findViewById(R.id.btn_done).setOnClickListener(this);
145//        findViewById(R.id.btn_discard).setOnClickListener(this);
146//
147//        // Catch clicks on the header views
148//        mList.setOnItemClickListener(this);
149//        mList.setOnCreateContextMenuListener(this);
150//
151//        mSortOrder = mContactsPrefs.getSortOrder();
152//        mDisplayOrder = mContactsPrefs.getDisplayOrder();
153//    }
154//
155//    private void createWithPhonesOnlyPreferenceView(LayoutInflater inflater) {
156//        // Add the "Only contacts with phones" header modifier.
157//        mHeaderPhones = inflater.inflate(R.layout.display_options_phones_only, mList, false);
158//        mHeaderPhones.setId(R.id.header_phones);
159//        mDisplayPhones = (CheckBox) mHeaderPhones.findViewById(android.R.id.checkbox);
160//        mDisplayPhones.setChecked(mPrefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
161//                Prefs.DISPLAY_ONLY_PHONES_DEFAULT));
162//        {
163//            final TextView text1 = (TextView)mHeaderPhones.findViewById(android.R.id.text1);
164//            final TextView text2 = (TextView)mHeaderPhones.findViewById(android.R.id.text2);
165//            text1.setText(R.string.showFilterPhones);
166//            text2.setText(R.string.showFilterPhonesDescrip);
167//        }
168//    }
169//
170//    private void createSortOrderPreferenceView(LayoutInflater inflater) {
171//        mSortOrderView = inflater.inflate(R.layout.preference_with_more_button, mList, false);
172//
173//        View preferenceLayout = mSortOrderView.findViewById(R.id.preference);
174//
175//        TextView label = (TextView)preferenceLayout.findViewById(R.id.label);
176//        label.setText(getString(R.string.display_options_sort_list_by));
177//
178//        mSortOrderTextView = (TextView)preferenceLayout.findViewById(R.id.data);
179//    }
180//
181//    private void createDisplayOrderPreferenceView(LayoutInflater inflater) {
182//        mDisplayOrderView = inflater.inflate(R.layout.preference_with_more_button, mList, false);
183//        View preferenceLayout = mDisplayOrderView.findViewById(R.id.preference);
184//
185//        TextView label = (TextView)preferenceLayout.findViewById(R.id.label);
186//        label.setText(getString(R.string.display_options_view_names_as));
187//
188//        mDisplayOrderTextView = (TextView)preferenceLayout.findViewById(R.id.data);
189//    }
190//
191//    private void createDisplayGroupHeader(LayoutInflater inflater) {
192//        // Add the separator before showing the detailed group list.
193//        mHeaderSeparator = inflater.inflate(R.layout.list_separator, mList, false);
194//        {
195//            final TextView text1 = (TextView)mHeaderSeparator;
196//            text1.setText(R.string.headerContactGroups);
197//        }
198//    }
199//
200//    @Override
201//    protected void onResume() {
202//        super.onResume();
203//        mList.removeHeaderView(mHeaderPhones);
204//        mList.removeHeaderView(mSortOrderView);
205//        mList.removeHeaderView(mDisplayOrderView);
206//        mList.removeHeaderView(mHeaderSeparator);
207//
208//        // List adapter needs to be reset, because header views cannot be added
209//        // to a list with an existing adapter.
210//        setListAdapter(null);
211//
212//        mList.addHeaderView(mHeaderPhones, null, true);
213//        if (mContactsPrefs.isSortOrderUserChangeable()) {
214//            mList.addHeaderView(mSortOrderView, null, true);
215//        }
216//
217//        if (mContactsPrefs.isSortOrderUserChangeable()) {
218//            mList.addHeaderView(mDisplayOrderView, null, true);
219//        }
220//
221//        mList.addHeaderView(mHeaderSeparator, null, false);
222//
223//        setListAdapter(mAdapter);
224//
225//        bindView();
226//
227//        // Start background query to find account details
228//        new QueryGroupsTask(this).execute();
229//    }
230//
231//    private void bindView() {
232//        mSortOrderTextView.setText(
233//                mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY
234//                        ? getString(R.string.display_options_sort_by_given_name)
235//                        : getString(R.string.display_options_sort_by_family_name));
236//
237//        mDisplayOrderTextView.setText(
238//                mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY
239//                        ? getString(R.string.display_options_view_given_name_first)
240//                        : getString(R.string.display_options_view_family_name_first));
241//    }
242//
243//    @Override
244//    protected Dialog onCreateDialog(int id, Bundle args) {
245//        switch (id) {
246//            case DIALOG_SORT_ORDER:
247//                return createSortOrderDialog();
248//            case DIALOG_DISPLAY_ORDER:
249//                return createDisplayOrderDialog();
250//        }
251//
252//        return null;
253//    }
254//
255//    private Dialog createSortOrderDialog() {
256//        String[] items = new String[] {
257//                getString(R.string.display_options_sort_by_given_name),
258//                getString(R.string.display_options_sort_by_family_name),
259//        };
260//
261//        return new AlertDialog.Builder(this)
262//            .setIcon(com.android.internal.R.drawable.ic_dialog_menu_generic)
263//            .setTitle(R.string.display_options_sort_list_by)
264//            .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
265//                    public void onClick(DialogInterface dialog, int whichButton) {
266//                        setSortOrder(dialog);
267//                        dialog.dismiss();
268//                    }
269//                })
270//            .setNegativeButton(android.R.string.cancel, null)
271//            .create();
272//    }
273//
274//    private Dialog createDisplayOrderDialog() {
275//        String[] items = new String[] {
276//                getString(R.string.display_options_view_given_name_first),
277//                getString(R.string.display_options_view_family_name_first),
278//        };
279//
280//        return new AlertDialog.Builder(this)
281//            .setIcon(com.android.internal.R.drawable.ic_dialog_menu_generic)
282//            .setTitle(R.string.display_options_view_names_as)
283//            .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
284//                    public void onClick(DialogInterface dialog, int whichButton) {
285//                        setDisplayOrder(dialog);
286//                        dialog.dismiss();
287//                    }
288//                })
289//            .setNegativeButton(android.R.string.cancel, null)
290//            .create();
291//    }
292//
293//    @Override
294//    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
295//        switch (id) {
296//            case DIALOG_SORT_ORDER:
297//                setCheckedItem(dialog,
298//                        mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY ? 0 : 1);
299//                break;
300//            case DIALOG_DISPLAY_ORDER:
301//                setCheckedItem(dialog,
302//                        mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY
303//                                ? 0 : 1);
304//                break;
305//        }
306//    }
307//
308//    private void setCheckedItem(Dialog dialog, int position) {
309//        ListView listView = ((AlertDialog)dialog).getListView();
310//        listView.setItemChecked(position, true);
311//        listView.setSelection(position);
312//    }
313//
314//    protected void setSortOrder(DialogInterface dialog) {
315//        ListView listView = ((AlertDialog)dialog).getListView();
316//        int checked = listView.getCheckedItemPosition();
317//        mSortOrder = checked == 0
318//                ? ContactsContract.Preferences.SORT_ORDER_PRIMARY
319//                : ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE;
320//
321//        bindView();
322//    }
323//
324//    protected void setDisplayOrder(DialogInterface dialog) {
325//        ListView listView = ((AlertDialog)dialog).getListView();
326//        int checked = listView.getCheckedItemPosition();
327//        mDisplayOrder = checked == 0
328//                ? ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY
329//                : ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE;
330//
331//        bindView();
332//    }
333//
334//    /**
335//     * Background operation to build set of {@link AccountDisplay} for each
336//     * {@link AccountTypes#getAccounts(boolean)} that provides groups.
337//     */
338//    private static class QueryGroupsTask extends
339//            WeakAsyncTask<Void, Void, AccountSet, ContactsPreferenceActivity> {
340//        public QueryGroupsTask(ContactsPreferenceActivity target) {
341//            super(target);
342//        }
343//
344//        @Override
345//        protected AccountSet doInBackground(ContactsPreferenceActivity target,
346//                Void... params) {
347//            final Context context = target;
348//            final AccountTypes sources = AccountTypes.getInstance(context);
349//            final ContentResolver resolver = context.getContentResolver();
350//
351//            // Inflate groups entry for each account
352//            final AccountSet accounts = new AccountSet();
353//            for (Account account : sources.getAccounts(false)) {
354//                accounts.add(new AccountDisplay(resolver, account.name, account.type));
355//            }
356//
357//            return accounts;
358//        }
359//
360//        @Override
361//        protected void onPostExecute(ContactsPreferenceActivity target, AccountSet result) {
362//            target.mAdapter.setAccounts(result);
363//        }
364//    }
365//
366//    private static final int DEFAULT_SHOULD_SYNC = 1;
367//    private static final int DEFAULT_VISIBLE = 0;
368//
369//    /**
370//     * Entry holding any changes to {@link Groups} or {@link Settings} rows,
371//     * such as {@link Groups#SHOULD_SYNC} or {@link Groups#GROUP_VISIBLE}.
372//     */
373//    protected static class GroupDelta extends ValuesDelta {
374//        private boolean mUngrouped = false;
375//        private boolean mAccountHasGroups;
376//
377//        private GroupDelta() {
378//            super();
379//        }
380//
381//        /**
382//         * Build {@link GroupDelta} from the {@link Settings} row for the given
383//         * {@link Settings#ACCOUNT_NAME} and {@link Settings#ACCOUNT_TYPE}.
384//         */
385//        public static GroupDelta fromSettings(ContentResolver resolver, String accountName,
386//                String accountType, boolean accountHasGroups) {
387//            final Uri settingsUri = Settings.CONTENT_URI.buildUpon()
388//                    .appendQueryParameter(Settings.ACCOUNT_NAME, accountName)
389//                    .appendQueryParameter(Settings.ACCOUNT_TYPE, accountType).build();
390//            final Cursor cursor = resolver.query(settingsUri, new String[] {
391//                    Settings.SHOULD_SYNC, Settings.UNGROUPED_VISIBLE
392//            }, null, null, null);
393//
394//            try {
395//                final ContentValues values = new ContentValues();
396//                values.put(Settings.ACCOUNT_NAME, accountName);
397//                values.put(Settings.ACCOUNT_TYPE, accountType);
398//
399//                if (cursor != null && cursor.moveToFirst()) {
400//                    // Read existing values when present
401//                    values.put(Settings.SHOULD_SYNC, cursor.getInt(0));
402//                    values.put(Settings.UNGROUPED_VISIBLE, cursor.getInt(1));
403//                    return fromBefore(values).setUngrouped(accountHasGroups);
404//                } else {
405//                    // Nothing found, so treat as create
406//                    values.put(Settings.SHOULD_SYNC, DEFAULT_SHOULD_SYNC);
407//                    values.put(Settings.UNGROUPED_VISIBLE, DEFAULT_VISIBLE);
408//                    return fromAfter(values).setUngrouped(accountHasGroups);
409//                }
410//            } finally {
411//                if (cursor != null) cursor.close();
412//            }
413//        }
414//
415//        public static GroupDelta fromBefore(ContentValues before) {
416//            final GroupDelta entry = new GroupDelta();
417//            entry.mBefore = before;
418//            entry.mAfter = new ContentValues();
419//            return entry;
420//        }
421//
422//        public static GroupDelta fromAfter(ContentValues after) {
423//            final GroupDelta entry = new GroupDelta();
424//            entry.mBefore = null;
425//            entry.mAfter = after;
426//            return entry;
427//        }
428//
429//        protected GroupDelta setUngrouped(boolean accountHasGroups) {
430//            mUngrouped = true;
431//            mAccountHasGroups = accountHasGroups;
432//            return this;
433//        }
434//
435//        @Override
436//        public boolean beforeExists() {
437//            return mBefore != null;
438//        }
439//
440//        public boolean getShouldSync() {
441//            return getAsInteger(mUngrouped ? Settings.SHOULD_SYNC : Groups.SHOULD_SYNC,
442//                    DEFAULT_SHOULD_SYNC) != 0;
443//        }
444//
445//        public boolean getVisible() {
446//            return getAsInteger(mUngrouped ? Settings.UNGROUPED_VISIBLE : Groups.GROUP_VISIBLE,
447//                    DEFAULT_VISIBLE) != 0;
448//        }
449//
450//        public void putShouldSync(boolean shouldSync) {
451//            put(mUngrouped ? Settings.SHOULD_SYNC : Groups.SHOULD_SYNC, shouldSync ? 1 : 0);
452//        }
453//
454//        public void putVisible(boolean visible) {
455//            put(mUngrouped ? Settings.UNGROUPED_VISIBLE : Groups.GROUP_VISIBLE, visible ? 1 : 0);
456//        }
457//
458//        private String getAccountType() {
459//            return (mBefore == null ? mAfter : mBefore).getAsString(Settings.ACCOUNT_TYPE);
460//        }
461//
462//        public CharSequence getTitle(Context context) {
463//            if (mUngrouped) {
464//                final String customAllContactsName =
465//                        LocalizedNameResolver.getAllContactsName(context, getAccountType());
466//                if (customAllContactsName != null) {
467//                    return customAllContactsName;
468//                }
469//                if (mAccountHasGroups) {
470//                    return context.getText(R.string.display_ungrouped);
471//                } else {
472//                    return context.getText(R.string.display_all_contacts);
473//                }
474//            } else {
475//                final Integer titleRes = getAsInteger(Groups.TITLE_RES);
476//                if (titleRes != null) {
477//                    final String packageName = getAsString(Groups.RES_PACKAGE);
478//                    return context.getPackageManager().getText(packageName, titleRes, null);
479//                } else {
480//                    return getAsString(Groups.TITLE);
481//                }
482//            }
483//        }
484//
485//        /**
486//         * Build a possible {@link ContentProviderOperation} to persist any
487//         * changes to the {@link Groups} or {@link Settings} row described by
488//         * this {@link GroupDelta}.
489//         */
490//        public ContentProviderOperation buildDiff() {
491//            if (isNoop()) {
492//                return null;
493//            } else if (isUpdate()) {
494//                // When has changes and "before" exists, then "update"
495//                final Builder builder = ContentProviderOperation
496//                        .newUpdate(mUngrouped ? Settings.CONTENT_URI : addCallerIsSyncAdapterParameter(Groups.CONTENT_URI));
497//                if (mUngrouped) {
498//                    builder.withSelection(Settings.ACCOUNT_NAME + "=? AND " + Settings.ACCOUNT_TYPE
499//                            + "=?", new String[] {
500//                            this.getAsString(Settings.ACCOUNT_NAME),
501//                            this.getAsString(Settings.ACCOUNT_TYPE)
502//                    });
503//                } else {
504//                    builder.withSelection(Groups._ID + "=" + this.getId(), null);
505//                }
506//                builder.withValues(mAfter);
507//                return builder.build();
508//            } else if (isInsert() && mUngrouped) {
509//                // Only allow inserts for Settings
510//                mAfter.remove(mIdColumn);
511//                final Builder builder = ContentProviderOperation.newInsert(Settings.CONTENT_URI);
512//                builder.withValues(mAfter);
513//                return builder.build();
514//            } else {
515//                throw new IllegalStateException("Unexpected delete or insert");
516//            }
517//        }
518//    }
519//
520//    private static Uri addCallerIsSyncAdapterParameter(Uri uri) {
521//        return uri.buildUpon()
522//	        .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
523//	        .build();
524//    }
525//
526//    /**
527//     * {@link Comparator} to sort by {@link Groups#_ID}.
528//     */
529//    private static Comparator<GroupDelta> sIdComparator = new Comparator<GroupDelta>() {
530//        public int compare(GroupDelta object1, GroupDelta object2) {
531//            final Long id1 = object1.getId();
532//            final Long id2 = object2.getId();
533//            if (id1 == null && id2 == null) {
534//                return 0;
535//            } else if (id1 == null) {
536//                return -1;
537//            } else if (id2 == null) {
538//                return 1;
539//            } else if (id1 < id2) {
540//                return -1;
541//            } else if (id1 > id2) {
542//                return 1;
543//            } else {
544//                return 0;
545//            }
546//        }
547//    };
548//
549//    /**
550//     * Set of all {@link AccountDisplay} entries, one for each source.
551//     */
552//    protected static class AccountSet extends ArrayList<AccountDisplay> {
553//        public ArrayList<ContentProviderOperation> buildDiff() {
554//            final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
555//            for (AccountDisplay account : this) {
556//                account.buildDiff(diff);
557//            }
558//            return diff;
559//        }
560//    }
561//
562//    /**
563//     * {@link GroupDelta} details for a single {@link Account}, usually shown as
564//     * children under a single expandable group.
565//     */
566//    protected static class AccountDisplay {
567//        public String mName;
568//        public String mType;
569//
570//        public GroupDelta mUngrouped;
571//        public ArrayList<GroupDelta> mSyncedGroups = Lists.newArrayList();
572//        public ArrayList<GroupDelta> mUnsyncedGroups = Lists.newArrayList();
573//
574//        /**
575//         * Build an {@link AccountDisplay} covering all {@link Groups} under the
576//         * given {@link Account}.
577//         */
578//        public AccountDisplay(ContentResolver resolver, String accountName, String accountType) {
579//            mName = accountName;
580//            mType = accountType;
581//
582//            final Uri groupsUri = Groups.CONTENT_URI.buildUpon()
583//                    .appendQueryParameter(Groups.ACCOUNT_NAME, accountName)
584//                    .appendQueryParameter(Groups.ACCOUNT_TYPE, accountType).build();
585//            EntityIterator iterator = ContactsContract.Groups.newEntityIterator(resolver.query(
586//                    groupsUri, null, null, null, null));
587//            try {
588//                boolean hasGroups = false;
589//
590//                // Create entries for each known group
591//                while (iterator.hasNext()) {
592//                    final ContentValues values = iterator.next().getEntityValues();
593//                    final GroupDelta group = GroupDelta.fromBefore(values);
594//                    addGroup(group);
595//                    hasGroups = true;
596//                }
597//                // Create single entry handling ungrouped status
598//                mUngrouped = GroupDelta.fromSettings(resolver, accountName, accountType, hasGroups);
599//                addGroup(mUngrouped);
600//            } finally {
601//                iterator.close();
602//            }
603//        }
604//
605//        /**
606//         * Add the given {@link GroupDelta} internally, filing based on its
607//         * {@link GroupDelta#getShouldSync()} status.
608//         */
609//        private void addGroup(GroupDelta group) {
610//            if (group.getShouldSync()) {
611//                mSyncedGroups.add(group);
612//            } else {
613//                mUnsyncedGroups.add(group);
614//            }
615//        }
616//
617//        /**
618//         * Set the {@link GroupDelta#putShouldSync(boolean)} value for all
619//         * children {@link GroupDelta} rows.
620//         */
621//        public void setShouldSync(boolean shouldSync) {
622//            final Iterator<GroupDelta> oppositeChildren = shouldSync ?
623//                    mUnsyncedGroups.iterator() : mSyncedGroups.iterator();
624//            while (oppositeChildren.hasNext()) {
625//                final GroupDelta child = oppositeChildren.next();
626//                setShouldSync(child, shouldSync, false);
627//                oppositeChildren.remove();
628//            }
629//        }
630//
631//        public void setShouldSync(GroupDelta child, boolean shouldSync) {
632//            setShouldSync(child, shouldSync, true);
633//        }
634//
635//        /**
636//         * Set {@link GroupDelta#putShouldSync(boolean)}, and file internally
637//         * based on updated state.
638//         */
639//        public void setShouldSync(GroupDelta child, boolean shouldSync, boolean attemptRemove) {
640//            child.putShouldSync(shouldSync);
641//            if (shouldSync) {
642//                if (attemptRemove) {
643//                    mUnsyncedGroups.remove(child);
644//                }
645//                mSyncedGroups.add(child);
646//                Collections.sort(mSyncedGroups, sIdComparator);
647//            } else {
648//                if (attemptRemove) {
649//                    mSyncedGroups.remove(child);
650//                }
651//                mUnsyncedGroups.add(child);
652//            }
653//        }
654//
655//        /**
656//         * Build set of {@link ContentProviderOperation} to persist any user
657//         * changes to {@link GroupDelta} rows under this {@link Account}.
658//         */
659//        public void buildDiff(ArrayList<ContentProviderOperation> diff) {
660//            for (GroupDelta group : mSyncedGroups) {
661//                final ContentProviderOperation oper = group.buildDiff();
662//                if (oper != null) diff.add(oper);
663//            }
664//            for (GroupDelta group : mUnsyncedGroups) {
665//                final ContentProviderOperation oper = group.buildDiff();
666//                if (oper != null) diff.add(oper);
667//            }
668//        }
669//    }
670//
671//    /**
672//     * {@link ExpandableListAdapter} that shows {@link GroupDelta} settings,
673//     * grouped by {@link Account} source. Shows footer row when any groups are
674//     * unsynced, as determined through {@link AccountDisplay#mUnsyncedGroups}.
675//     */
676//    protected static class DisplayAdapter extends BaseExpandableListAdapter {
677//        private Context mContext;
678//        private LayoutInflater mInflater;
679//        private AccountTypes mSources;
680//        private AccountSet mAccounts;
681//
682//        private boolean mChildWithPhones = false;
683//
684//        public DisplayAdapter(Context context) {
685//            mContext = context;
686//            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
687//            mSources = AccountTypes.getInstance(context);
688//        }
689//
690//        public void setAccounts(AccountSet accounts) {
691//            mAccounts = accounts;
692//            notifyDataSetChanged();
693//        }
694//
695//        /**
696//         * In group descriptions, show the number of contacts with phone
697//         * numbers, in addition to the total contacts.
698//         */
699//        public void setChildDescripWithPhones(boolean withPhones) {
700//            mChildWithPhones = withPhones;
701//        }
702//
703//        /** {@inheritDoc} */
704//        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
705//                View convertView, ViewGroup parent) {
706//            if (convertView == null) {
707//                convertView = mInflater.inflate(R.layout.display_child, parent, false);
708//            }
709//
710//            final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
711//            final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
712//            final CheckBox checkbox = (CheckBox)convertView.findViewById(android.R.id.checkbox);
713//
714//            final AccountDisplay account = mAccounts.get(groupPosition);
715//            final GroupDelta child = (GroupDelta)this.getChild(groupPosition, childPosition);
716//            if (child != null) {
717//                // Handle normal group, with title and checkbox
718//                final boolean groupVisible = child.getVisible();
719//                checkbox.setVisibility(View.VISIBLE);
720//                checkbox.setChecked(groupVisible);
721//
722//                final CharSequence groupTitle = child.getTitle(mContext);
723//                text1.setText(groupTitle);
724//
725////              final int count = cursor.getInt(GroupsQuery.SUMMARY_COUNT);
726////              final int withPhones = cursor.getInt(GroupsQuery.SUMMARY_WITH_PHONES);
727//
728////              final CharSequence descrip = mContext.getResources().getQuantityString(
729////                      mChildWithPhones ? R.plurals.groupDescripPhones : R.plurals.groupDescrip,
730////                      count, count, withPhones);
731//
732////              text2.setText(descrip);
733//                text2.setVisibility(View.GONE);
734//            } else {
735//                // When unknown child, this is "more" footer view
736//                checkbox.setVisibility(View.GONE);
737//                text1.setText(R.string.display_more_groups);
738//                text2.setVisibility(View.GONE);
739//            }
740//
741//            return convertView;
742//        }
743//
744//        /** {@inheritDoc} */
745//        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
746//                ViewGroup parent) {
747//            if (convertView == null) {
748//                convertView = mInflater.inflate(R.layout.display_group, parent, false);
749//            }
750//
751//            final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
752//            final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
753//
754//            final AccountDisplay account = (AccountDisplay)this.getGroup(groupPosition);
755//
756//            final BaseAccountType source = mSources.getInflatedSource(account.mType,
757//                    BaseAccountType.LEVEL_SUMMARY);
758//
759//            text1.setText(account.mName);
760//            text2.setText(source.getDisplayLabel(mContext));
761//            text2.setVisibility(account.mName == null ? View.GONE : View.VISIBLE);
762//
763//            return convertView;
764//        }
765//
766//        /** {@inheritDoc} */
767//        public Object getChild(int groupPosition, int childPosition) {
768//            final AccountDisplay account = mAccounts.get(groupPosition);
769//            final boolean validChild = childPosition >= 0
770//                    && childPosition < account.mSyncedGroups.size();
771//            if (validChild) {
772//                return account.mSyncedGroups.get(childPosition);
773//            } else {
774//                return null;
775//            }
776//        }
777//
778//        /** {@inheritDoc} */
779//        public long getChildId(int groupPosition, int childPosition) {
780//            final GroupDelta child = (GroupDelta)getChild(groupPosition, childPosition);
781//            if (child != null) {
782//                final Long childId = child.getId();
783//                return childId != null ? childId : Long.MIN_VALUE;
784//            } else {
785//                return Long.MIN_VALUE;
786//            }
787//        }
788//
789//        /** {@inheritDoc} */
790//        public int getChildrenCount(int groupPosition) {
791//            // Count is any synced groups, plus possible footer
792//            final AccountDisplay account = mAccounts.get(groupPosition);
793//            final boolean anyHidden = account.mUnsyncedGroups.size() > 0;
794//            return account.mSyncedGroups.size() + (anyHidden ? 1 : 0);
795//        }
796//
797//        /** {@inheritDoc} */
798//        public Object getGroup(int groupPosition) {
799//            return mAccounts.get(groupPosition);
800//        }
801//
802//        /** {@inheritDoc} */
803//        public int getGroupCount() {
804//            if (mAccounts == null) {
805//                return 0;
806//            }
807//            return mAccounts.size();
808//        }
809//
810//        /** {@inheritDoc} */
811//        public long getGroupId(int groupPosition) {
812//            return groupPosition;
813//        }
814//
815//        /** {@inheritDoc} */
816//        public boolean hasStableIds() {
817//            return true;
818//        }
819//
820//        /** {@inheritDoc} */
821//        public boolean isChildSelectable(int groupPosition, int childPosition) {
822//            return true;
823//        }
824//    }
825//
826//    /**
827//     * Handle any clicks on header views added to our {@link #mAdapter}, which
828//     * are usually the global modifier checkboxes.
829//     */
830//    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
831//        Log.d(TAG, "OnItemClick, position=" + position + ", id=" + id);
832//        if (view == mHeaderPhones) {
833//            mDisplayPhones.toggle();
834//            return;
835//        }
836//        if (view == mDisplayOrderView) {
837//            Log.d(TAG, "Showing Display Order dialog");
838//            showDialog(DIALOG_DISPLAY_ORDER);
839//            return;
840//        }
841//        if (view == mSortOrderView) {
842//            Log.d(TAG, "Showing Sort Order dialog");
843//            showDialog(DIALOG_SORT_ORDER);
844//            return;
845//        }
846//    }
847//
848//    /** {@inheritDoc} */
849//    public void onClick(View view) {
850//        switch (view.getId()) {
851//            case R.id.btn_done: {
852//                this.doSaveAction();
853//                break;
854//            }
855//            case R.id.btn_discard: {
856//                this.finish();
857//                break;
858//            }
859//        }
860//    }
861//
862//    /**
863//     * Assign a specific value to {@link Prefs#DISPLAY_ONLY_PHONES}, refreshing
864//     * the visible list as needed.
865//     */
866//    protected void setDisplayOnlyPhones(boolean displayOnlyPhones) {
867//        mDisplayPhones.setChecked(displayOnlyPhones);
868//
869//        Editor editor = mPrefs.edit();
870//        editor.putBoolean(Prefs.DISPLAY_ONLY_PHONES, displayOnlyPhones);
871//        editor.apply();
872//
873//        mAdapter.setChildDescripWithPhones(displayOnlyPhones);
874//        mAdapter.notifyDataSetChanged();
875//    }
876//
877//    /**
878//     * Handle any clicks on {@link ExpandableListAdapter} children, which
879//     * usually mean toggling its visible state.
880//     */
881//    @Override
882//    public boolean onChildClick(ExpandableListView parent, View view, int groupPosition,
883//            int childPosition, long id) {
884//        final CheckBox checkbox = (CheckBox)view.findViewById(android.R.id.checkbox);
885//
886//        final AccountDisplay account = (AccountDisplay)mAdapter.getGroup(groupPosition);
887//        final GroupDelta child = (GroupDelta)mAdapter.getChild(groupPosition, childPosition);
888//        if (child != null) {
889//            checkbox.toggle();
890//            child.putVisible(checkbox.isChecked());
891//        } else {
892//            // Open context menu for bringing back unsynced
893//            this.openContextMenu(view);
894//        }
895//        return true;
896//    }
897//
898//    // TODO: move these definitions to framework constants when we begin
899//    // defining this mode through <sync-adapter> tags
900//    private static final int SYNC_MODE_UNSUPPORTED = 0;
901//    private static final int SYNC_MODE_UNGROUPED = 1;
902//    private static final int SYNC_MODE_EVERYTHING = 2;
903//
904//    protected int getSyncMode(AccountDisplay account) {
905//        // TODO: read sync mode through <sync-adapter> definition
906//        if (GoogleAccountType.ACCOUNT_TYPE.equals(account.mType)) {
907//            return SYNC_MODE_EVERYTHING;
908//        } else {
909//            return SYNC_MODE_UNSUPPORTED;
910//        }
911//    }
912//
913//    @Override
914//    public void onCreateContextMenu(ContextMenu menu, View view,
915//            ContextMenu.ContextMenuInfo menuInfo) {
916//        super.onCreateContextMenu(menu, view, menuInfo);
917//
918//        // Bail if not working with expandable long-press, or if not child
919//        if (!(menuInfo instanceof ExpandableListContextMenuInfo)) return;
920//
921//        final ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
922//        final int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
923//        final int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
924//
925//        // Skip long-press on expandable parents
926//        if (childPosition == -1) return;
927//
928//        final AccountDisplay account = (AccountDisplay)mAdapter.getGroup(groupPosition);
929//        final GroupDelta child = (GroupDelta)mAdapter.getChild(groupPosition, childPosition);
930//
931//        // Ignore when selective syncing unsupported
932//        final int syncMode = getSyncMode(account);
933//        if (syncMode == SYNC_MODE_UNSUPPORTED) return;
934//
935//        if (child != null) {
936//            showRemoveSync(menu, account, child, syncMode);
937//        } else {
938//            showAddSync(menu, account, syncMode);
939//        }
940//    }
941//
942//    protected void showRemoveSync(ContextMenu menu, final AccountDisplay account,
943//            final GroupDelta child, final int syncMode) {
944//        final CharSequence title = child.getTitle(this);
945//
946//        menu.setHeaderTitle(title);
947//        menu.add(R.string.menu_sync_remove).setOnMenuItemClickListener(
948//                new OnMenuItemClickListener() {
949//                    public boolean onMenuItemClick(MenuItem item) {
950//                        handleRemoveSync(account, child, syncMode, title);
951//                        return true;
952//                    }
953//                });
954//    }
955//
956//    protected void handleRemoveSync(final AccountDisplay account, final GroupDelta child,
957//            final int syncMode, CharSequence title) {
958//        final boolean shouldSyncUngrouped = account.mUngrouped.getShouldSync();
959//        if (syncMode == SYNC_MODE_EVERYTHING && shouldSyncUngrouped
960//                && !child.equals(account.mUngrouped)) {
961//            // Warn before removing this group when it would cause ungrouped to stop syncing
962//            final AlertDialog.Builder builder = new AlertDialog.Builder(this);
963//            final CharSequence removeMessage = this.getString(
964//                    R.string.display_warn_remove_ungrouped, title);
965//            builder.setTitle(R.string.menu_sync_remove);
966//            builder.setMessage(removeMessage);
967//            builder.setNegativeButton(android.R.string.cancel, null);
968//            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
969//                public void onClick(DialogInterface dialog, int which) {
970//                    // Mark both this group and ungrouped to stop syncing
971//                    account.setShouldSync(account.mUngrouped, false);
972//                    account.setShouldSync(child, false);
973//                    mAdapter.notifyDataSetChanged();
974//                }
975//            });
976//            builder.show();
977//        } else {
978//            // Mark this group to not sync
979//            account.setShouldSync(child, false);
980//            mAdapter.notifyDataSetChanged();
981//        }
982//    }
983//
984//    protected void showAddSync(ContextMenu menu, final AccountDisplay account, final int syncMode) {
985//        menu.setHeaderTitle(R.string.dialog_sync_add);
986//
987//        // Create item for each available, unsynced group
988//        for (final GroupDelta child : account.mUnsyncedGroups) {
989//            if (!child.getShouldSync()) {
990//                final CharSequence title = child.getTitle(this);
991//                menu.add(title).setOnMenuItemClickListener(new OnMenuItemClickListener() {
992//                    public boolean onMenuItemClick(MenuItem item) {
993//                        // Adding specific group for syncing
994//                        if (child.mUngrouped && syncMode == SYNC_MODE_EVERYTHING) {
995//                            account.setShouldSync(true);
996//                        } else {
997//                            account.setShouldSync(child, true);
998//                        }
999//                        mAdapter.notifyDataSetChanged();
1000//                        return true;
1001//                    }
1002//                });
1003//            }
1004//        }
1005//    }
1006//
1007//    /** {@inheritDoc} */
1008//    @Override
1009//    public void onBackPressed() {
1010//        doSaveAction();
1011//    }
1012//
1013//    private void doSaveAction() {
1014//        mContactsPrefs.setSortOrder(mSortOrder);
1015//        mContactsPrefs.setDisplayOrder(mDisplayOrder);
1016//
1017//        if (mAdapter == null || mAdapter.mAccounts == null) {
1018//            return;
1019//        }
1020//        setDisplayOnlyPhones(mDisplayPhones.isChecked());
1021//        new UpdateTask(this).execute(mAdapter.mAccounts);
1022//    }
1023//
1024//    /**
1025//     * Background task that persists changes to {@link Groups#GROUP_VISIBLE},
1026//     * showing spinner dialog to user while updating.
1027//     */
1028//    public static class UpdateTask extends
1029//            WeakAsyncTask<AccountSet, Void, Void, Activity> {
1030//        private WeakReference<ProgressDialog> mProgress;
1031//
1032//        public UpdateTask(Activity target) {
1033//            super(target);
1034//        }
1035//
1036//        /** {@inheritDoc} */
1037//        @Override
1038//        protected void onPreExecute(Activity target) {
1039//            final Context context = target;
1040//
1041//            mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(context, null,
1042//                    context.getText(R.string.savingDisplayGroups)));
1043//
1044//            // Before starting this task, start an empty service to protect our
1045//            // process from being reclaimed by the system.
1046//            context.startService(new Intent(context, EmptyService.class));
1047//        }
1048//
1049//        /** {@inheritDoc} */
1050//        @Override
1051//        protected Void doInBackground(Activity target, AccountSet... params) {
1052//            final Context context = target;
1053//            final ContentValues values = new ContentValues();
1054//            final ContentResolver resolver = context.getContentResolver();
1055//
1056//            try {
1057//                // Build changes and persist in transaction
1058//                final AccountSet set = params[0];
1059//                final ArrayList<ContentProviderOperation> diff = set.buildDiff();
1060//                resolver.applyBatch(ContactsContract.AUTHORITY, diff);
1061//            } catch (RemoteException e) {
1062//                Log.e(TAG, "Problem saving display groups", e);
1063//            } catch (OperationApplicationException e) {
1064//                Log.e(TAG, "Problem saving display groups", e);
1065//            }
1066//
1067//            return null;
1068//        }
1069//
1070//        /** {@inheritDoc} */
1071//        @Override
1072//        protected void onPostExecute(Activity target, Void result) {
1073//            final Context context = target;
1074//
1075//            final ProgressDialog dialog = mProgress.get();
1076//            if (dialog != null) {
1077//                try {
1078//                    dialog.dismiss();
1079//                } catch (Exception e) {
1080//                    Log.e(TAG, "Error dismissing progress dialog", e);
1081//                }
1082//            }
1083//
1084//            target.finish();
1085//
1086//            // Stop the service that was protecting us
1087//            context.stopService(new Intent(context, EmptyService.class));
1088//        }
1089//    }
1090//
1091//    @Override
1092//    public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
1093//            boolean globalSearch) {
1094//        if (globalSearch) {
1095//            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1096//        } else {
1097//            ContactsSearchManager.startSearch(this, initialQuery);
1098//        }
1099//    }
1100}
1101