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